From 7fc545d55d2c1e4298d5fc3040c926ae0165f873 Mon Sep 17 00:00:00 2001 From: Philipp Zieris <37868570+pzieris@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:54:14 +0100 Subject: [PATCH] Initial commit --- .dockerignore | 28 + .gitignore | 30 + .gitmodules | 2 + LICENSE.txt | 202 ++ ...-benchmark-2024-08-23 11:35:36.205608.json | 1 + ...-benchmark-2024-08-23 11:36:34.252069.json | 1 + ...-benchmark-2024-08-23 11:37:32.228433.json | 1 + ...-benchmark-2024-08-23 11:38:31.502479.json | 1 + ...-benchmark-2024-08-23 11:39:29.665462.json | 1 + ...-benchmark-2024-08-23 11:40:27.522857.json | 1 + ...-benchmark-2024-08-23 11:41:24.866985.json | 1 + ...-benchmark-2024-08-23 11:42:22.826663.json | 1 + ...-benchmark-2024-08-23 11:43:21.808797.json | 1 + ...-benchmark-2024-08-23 11:44:19.972532.json | 1 + ...-benchmark-2024-08-23 11:45:18.033419.json | 1 + ...-benchmark-2024-08-23 11:46:16.094788.json | 1 + ...-benchmark-2024-08-23 11:47:14.155796.json | 1 + ...-benchmark-2024-08-23 11:48:12.217400.json | 1 + ...-benchmark-2024-08-23 11:49:10.278672.json | 1 + ...-benchmark-2024-08-23 11:50:08.440536.json | 1 + ...-benchmark-2024-08-23 11:51:06.503707.json | 1 + ...-benchmark-2024-08-23 11:52:04.667937.json | 1 + ...-benchmark-2024-08-23 11:53:02.554193.json | 1 + ...-benchmark-2024-08-23 11:53:59.971332.json | 1 + ...-benchmark-2024-08-23 11:54:58.135862.json | 1 + benchmark-output/benchmarks-size.json | 1 + ...-benchmark-2024-08-20 07:15:36.214852.json | 1 + ...-benchmark-2024-08-20 07:16:39.421518.json | 1 + ...-benchmark-2024-08-20 07:17:33.877914.json | 1 + ...-benchmark-2024-08-20 07:18:27.641227.json | 1 + ...-benchmark-2024-08-20 07:19:22.223523.json | 1 + ...-benchmark-2024-08-20 07:22:22.761514.json | 1 + ...-benchmark-2024-08-20 07:23:17.150873.json | 1 + ...-benchmark-2024-08-20 07:24:12.539264.json | 1 + ...-benchmark-2024-08-20 07:25:08.425179.json | 1 + ...-benchmark-2024-08-20 07:26:04.337105.json | 1 + ...-benchmark-2024-08-20 07:27:00.148576.json | 1 + ...-benchmark-2024-08-20 07:27:55.237711.json | 1 + ...-benchmark-2024-08-20 07:28:50.945571.json | 1 + ...-benchmark-2024-08-20 07:29:46.754391.json | 1 + ...-benchmark-2024-08-20 07:30:40.412155.json | 1 + ...-benchmark-2024-08-20 07:31:35.143103.json | 1 + ...-benchmark-2024-08-20 07:32:28.985440.json | 1 + ...-benchmark-2024-08-20 07:33:22.107728.json | 1 + ...-benchmark-2024-08-20 07:34:16.072524.json | 1 + ...-benchmark-2024-08-20 07:36:24.696037.json | 1 + ...-benchmark-2024-08-20 07:37:17.733229.json | 1 + ...-benchmark-2024-08-20 07:38:10.775495.json | 1 + ...-benchmark-2024-08-20 07:39:03.822099.json | 1 + ...-benchmark-2024-08-20 07:39:56.865710.json | 1 + ...-benchmark-2024-08-20 08:28:50.463366.json | 1 + ...-benchmark-2024-08-20 08:29:42.892983.json | 1 + ...-benchmark-2024-08-20 08:30:36.962670.json | 1 + ...-benchmark-2024-08-20 08:31:31.136874.json | 1 + ...-benchmark-2024-08-20 08:32:24.280363.json | 1 + ...-benchmark-2024-08-20 08:34:02.378899.json | 1 + ...-benchmark-2024-08-20 08:34:55.625728.json | 1 + ...-benchmark-2024-08-20 08:35:48.872563.json | 1 + ...-benchmark-2024-08-20 08:36:43.145201.json | 1 + ...-benchmark-2024-08-20 08:37:36.392582.json | 1 + ...-benchmark-2024-08-20 08:38:29.743328.json | 1 + ...-benchmark-2024-08-20 08:39:24.220492.json | 1 + ...-benchmark-2024-08-20 08:40:18.185840.json | 1 + ...-benchmark-2024-08-20 08:41:12.867675.json | 1 + ...-benchmark-2024-08-20 08:42:07.437976.json | 1 + ...-benchmark-2024-08-20 08:43:02.032717.json | 1 + ...-benchmark-2024-08-20 08:43:55.949029.json | 1 + ...-benchmark-2024-08-20 08:44:49.855015.json | 1 + ...-benchmark-2024-08-20 08:45:44.434503.json | 1 + ...-benchmark-2024-08-20 08:46:39.117894.json | 1 + ...-benchmark-2024-08-20 08:47:34.209877.json | 1 + ...-benchmark-2024-08-20 08:48:28.789175.json | 1 + ...-benchmark-2024-08-20 08:49:24.085695.json | 1 + ...-benchmark-2024-08-20 08:50:19.382249.json | 1 + ...-benchmark-2024-08-20 08:51:14.650965.json | 1 + ...-benchmark-2024-08-20 08:52:10.182122.json | 1 + ...-benchmark-2024-08-20 08:53:04.763449.json | 1 + ...-benchmark-2024-08-20 08:54:00.160700.json | 1 + ...-benchmark-2024-08-20 08:54:54.841759.json | 1 + ...-benchmark-2024-08-20 08:55:50.241128.json | 1 + ...-benchmark-2024-08-20 08:56:45.536299.json | 1 + ...-benchmark-2024-08-20 08:57:40.118298.json | 1 + ...-benchmark-2024-08-20 08:58:35.186132.json | 1 + ...-benchmark-2024-08-20 08:59:31.019219.json | 1 + ...-benchmark-2024-08-20 09:00:27.237984.json | 1 + ...-benchmark-2024-08-20 09:01:23.149472.json | 1 + ...-benchmark-2024-08-20 09:02:19.674217.json | 1 + ...-benchmark-2024-08-20 09:03:24.598129.json | 1 + ...-benchmark-2024-08-20 09:04:21.226169.json | 1 + ...-benchmark-2024-08-20 09:05:17.137034.json | 1 + ...-benchmark-2024-08-20 09:06:13.048665.json | 1 + ...-benchmark-2024-08-20 09:07:09.061002.json | 1 + ...-benchmark-2024-08-20 09:08:05.015132.json | 1 + ...-benchmark-2024-08-20 09:09:00.881587.json | 1 + ...-benchmark-2024-08-20 09:09:57.487831.json | 1 + ...-benchmark-2024-08-20 09:10:54.033808.json | 1 + ...-benchmark-2024-08-20 09:11:50.558019.json | 1 + ...-benchmark-2024-08-20 09:12:47.697540.json | 1 + ...-benchmark-2024-08-20 09:13:44.324380.json | 1 + ...-benchmark-2024-08-20 09:14:40.746971.json | 1 + ...-benchmark-2024-08-20 09:15:38.176186.json | 1 + ...-benchmark-2024-08-20 09:16:35.334034.json | 1 + ...-benchmark-2024-08-20 09:17:31.844067.json | 1 + ...-benchmark-2024-08-20 09:18:28.999147.json | 1 + ...-benchmark-2024-08-20 09:19:25.625981.json | 1 + ...-benchmark-2024-08-20 09:20:31.062208.json | 1 + ...-benchmark-2024-08-20 09:21:28.611003.json | 1 + ...-benchmark-2024-08-20 09:22:25.340802.json | 1 + ...-benchmark-2024-08-20 09:23:22.684988.json | 1 + ...-benchmark-2024-08-20 09:24:20.949997.json | 1 + ...-benchmark-2024-08-20 09:25:17.577855.json | 1 + ...-benchmark-2024-08-20 09:26:15.844016.json | 1 + ...-benchmark-2024-08-20 09:27:12.474049.json | 1 + ...-benchmark-2024-08-20 09:28:09.714267.json | 1 + ...-benchmark-2024-08-20 09:29:06.342918.json | 1 + ...-benchmark-2024-08-20 09:30:02.885976.json | 1 + ...-benchmark-2024-08-20 09:31:00.110025.json | 1 + ...-benchmark-2024-08-20 09:31:57.370331.json | 1 + ...-benchmark-2024-08-20 09:32:55.619965.json | 1 + ...-benchmark-2024-08-20 09:33:52.964818.json | 1 + ...-benchmark-2024-08-20 09:34:50.308819.json | 1 + ...-benchmark-2024-08-20 09:35:47.567040.json | 1 + ...-benchmark-2024-08-20 09:36:44.081613.json | 1 + ...-benchmark-2024-08-20 09:37:41.421666.json | 1 + ...-benchmark-2024-08-20 09:38:38.049922.json | 1 + ...-benchmark-2024-08-20 09:39:35.366876.json | 1 + ...-benchmark-2024-08-20 09:40:32.950956.json | 1 + ...-benchmark-2024-08-20 09:41:29.679246.json | 1 + ...-benchmark-2024-08-20 09:42:27.020558.json | 1 + ...-benchmark-2024-08-20 09:43:24.365362.json | 1 + benchmarking/.gitignore | 3 + benchmarking/benchmark_server.py | 240 +++ benchmarking/performance-benchmarks.ipynb | 899 ++++++++ benchmarking/requirements.txt | 3 + benchmarking/size-benchmarks.ipynb | 489 +++++ bt2x-embassy-std/.dockerignore | 16 + bt2x-embassy-std/Cargo.lock | 1822 +++++++++++++++++ bt2x-embassy-std/Cargo.toml | 37 + bt2x-embassy-std/LICENSE.txt | 202 ++ bt2x-embassy-std/deny.toml | 238 +++ bt2x-embassy-std/readme.md | 20 + bt2x-embassy-std/src/main.rs | 161 ++ bt2x-embedded-ffi/.cargo/config.toml | 16 + bt2x-embedded-ffi/.dockerignore | 16 + bt2x-embedded-ffi/.gitignore | 16 + bt2x-embedded-ffi/Cargo.toml | 48 + bt2x-embedded-ffi/LICENSE.txt | 202 ++ bt2x-embedded-ffi/bindings.h | 20 + bt2x-embedded-ffi/build.rs | 17 + bt2x-embedded-ffi/deny.toml | 237 +++ bt2x-embedded-ffi/rust-toolchain.toml | 2 + bt2x-embedded-ffi/src/lib.rs | 88 + bt2x-pi-pico-w/.cargo/config.toml | 30 + bt2x-pi-pico-w/.dockerignore | 16 + bt2x-pi-pico-w/.gitignore | 17 + bt2x-pi-pico-w/Cargo.toml | 177 ++ bt2x-pi-pico-w/LICENSE.txt | 202 ++ bt2x-pi-pico-w/README.md | 52 + bt2x-pi-pico-w/build.rs | 37 + bt2x-pi-pico-w/deny.toml | 238 +++ bt2x-pi-pico-w/firmware/43439A0.bin | Bin 0 -> 224190 bytes bt2x-pi-pico-w/firmware/43439A0_clm.bin | Bin 0 -> 4752 bytes .../LICENSE-permissive-binary-license-1.0.txt | 49 + bt2x-pi-pico-w/firmware/README.md | 5 + bt2x-pi-pico-w/memory.x | 27 + bt2x-pi-pico-w/random-bytes | 3 + bt2x-pi-pico-w/rust-toolchain.toml | 4 + .../src/benchmarking/code-size-full.rs | 253 +++ .../src/benchmarking/code-size-no-tuf.rs | 216 ++ .../code-size-simple-signature.rs | 204 ++ .../src/benchmarking/code-size-update-only.rs | 193 ++ .../src/benchmarking/performance.rs | 338 +++ bt2x-pi-pico-w/src/blinky.rs | 86 + bt2x-pi-pico-w/src/main.rs | 252 +++ bt2x-pi-pico/.cargo/config.toml | 29 + bt2x-pi-pico/.dockerignore | 16 + bt2x-pi-pico/.github/workflows/ci_checks.yml | 66 + bt2x-pi-pico/.gitignore | 14 + bt2x-pi-pico/.vscode/launch.json | 45 + bt2x-pi-pico/.vscode/settings.json | 5 + bt2x-pi-pico/Cargo.toml | 83 + bt2x-pi-pico/Embed.toml | 39 + bt2x-pi-pico/README.md | 267 +++ bt2x-pi-pico/build.rs | 31 + bt2x-pi-pico/debug_probes.md | 47 + bt2x-pi-pico/memory.x | 15 + bt2x-pi-pico/rust-toolchain.toml | 3 + bt2x-pi-pico/src/main.rs | 101 + bt2x/.dockerignore | 16 + bt2x/.gitignore | 19 + bt2x/Cargo.toml | 76 + bt2x/LICENSE.txt | 202 ++ bt2x/README.md | 13 + bt2x/bt2x-cli/Cargo.toml | 29 + bt2x/bt2x-cli/src/main.rs | 357 ++++ bt2x/bt2x-common/Cargo.toml | 59 + bt2x/bt2x-common/src/artifact/mod.rs | 1 + .../bt2x-common/src/artifact/source/bundle.rs | 54 + bt2x/bt2x-common/src/artifact/source/mod.rs | 15 + bt2x/bt2x-common/src/artifact/source/oci.rs | 115 ++ bt2x/bt2x-common/src/error.rs | 55 + bt2x/bt2x-common/src/gossip.rs | 278 +++ bt2x/bt2x-common/src/lib.rs | 21 + bt2x/bt2x-common/src/merkle/mod.rs | 6 + bt2x/bt2x-common/src/merkle/rfc6962.rs | 103 + bt2x/bt2x-common/src/merkle/tree.rs | 95 + bt2x/bt2x-common/src/merkle/verify.rs | 178 ++ bt2x/bt2x-common/src/rekor.rs | 106 + bt2x/bt2x-common/src/sct.rs | 59 + bt2x/bt2x-common/src/serde.rs | 225 ++ bt2x/bt2x-common/src/sigstore_config.rs | 137 ++ bt2x/bt2x-common/src/tuf/mod.rs | 142 ++ bt2x/bt2x-common/src/verifier/bt.rs | 370 ++++ bt2x/bt2x-common/src/verifier/mod.rs | 30 + bt2x/bt2x-embedded/.gitignore | 2 + bt2x/bt2x-embedded/Cargo.toml | 42 + bt2x/bt2x-embedded/src/bundle/mod.rs | 0 bt2x/bt2x-embedded/src/lib.rs | 627 ++++++ bt2x/bt2x-embedded/src/models/alpine.rs | 39 + .../bt2x-embedded/src/models/alpine_all_of.rs | 32 + .../src/models/consistency_proof.rs | 41 + bt2x/bt2x-embedded/src/models/cose.rs | 36 + bt2x/bt2x-embedded/src/models/cose_all_of.rs | 32 + bt2x/bt2x-embedded/src/models/error.rs | 31 + bt2x/bt2x-embedded/src/models/hashedrekord.rs | 28 + .../src/models/hashedrekord_all_of.rs | 32 + bt2x/bt2x-embedded/src/models/helm.rs | 48 + bt2x/bt2x-embedded/src/models/helm_all_of.rs | 32 + .../src/models/inactive_shard_log_info.rs | 44 + .../src/models/inclusion_proof.rs | 54 + bt2x/bt2x-embedded/src/models/intoto.rs | 36 + .../bt2x-embedded/src/models/intoto_all_of.rs | 32 + bt2x/bt2x-embedded/src/models/jar.rs | 34 + bt2x/bt2x-embedded/src/models/jar_all_of.rs | 32 + .../src/models/log_entry_value.rs | 176 ++ .../src/models/log_entry_value_attestation.rs | 28 + .../models/log_entry_value_verification.rs | 32 + bt2x/bt2x-embedded/src/models/log_info.rs | 51 + bt2x/bt2x-embedded/src/models/mod.rs | 45 + .../src/models/proposed_entry.rs | 109 + bt2x/bt2x-embedded/src/models/rekord.rs | 34 + .../bt2x-embedded/src/models/rekord_all_of.rs | 32 + bt2x/bt2x-embedded/src/models/rfc3161.rs | 31 + .../src/models/rfc3161_all_of.rs | 32 + bt2x/bt2x-embedded/src/models/rpm.rs | 37 + bt2x/bt2x-embedded/src/models/rpm_all_of.rs | 32 + .../models/schema/alpine_v0_0_1_schema.json | 67 + .../src/models/schema/cose_v0_0_1_schema.json | 81 + .../schema/hashedrekord_v0_0_1_schema.json | 54 + .../src/models/schema/helm_v0_0_1_schema.json | 82 + .../models/schema/intoto_v0_0_1_schema.json | 70 + .../src/models/schema/jar_v0_0_1_schema.json | 72 + .../models/schema/rekord_v0_0_1_schema.json | 76 + .../models/schema/rfc3161_v0_0_1_schema.json | 22 + .../src/models/schema/rpm_v0_0_1_schema.json | 66 + .../src/models/schema/tuf_v0_0_1_schema.json | 39 + bt2x/bt2x-embedded/src/models/search_index.rs | 62 + .../src/models/search_index_public_key.rs | 55 + .../src/models/search_log_query.rs | 39 + bt2x/bt2x-embedded/src/models/tuf.rs | 34 + bt2x/bt2x-embedded/src/models/tuf_all_of.rs | 32 + bt2x/bt2x-flasher/Cargo.toml | 29 + bt2x/bt2x-flasher/src/args.rs | 57 + bt2x/bt2x-flasher/src/main.rs | 244 +++ bt2x/bt2x-monitor/Cargo.toml | 31 + bt2x/bt2x-monitor/README.md | 26 + bt2x/bt2x-monitor/src/cli.rs | 27 + bt2x/bt2x-monitor/src/lib.rs | 286 +++ bt2x/bt2x-monitor/src/main.rs | 9 + bt2x/bt2x-ota-common/Cargo.toml | 36 + bt2x/bt2x-ota-common/src/flash.rs | 75 + bt2x/bt2x-ota-common/src/lib.rs | 683 ++++++ bt2x/bt2x-ota-common/src/net.rs | 184 ++ bt2x/bt2x-ota/Cargo.toml | 17 + bt2x/bt2x-ota/src/cli.rs | 13 + bt2x/bt2x-ota/src/main.rs | 329 +++ bt2x/bt2x-server/Cargo.toml | 31 + bt2x/bt2x-server/src/cli.rs | 53 + bt2x/bt2x-server/src/config.rs | 83 + bt2x/bt2x-server/src/main.rs | 328 +++ bt2x/deny.toml | 230 +++ bt2x/rust-toolchain.toml | 2 + build-docs.sh | 208 ++ configs/bt2x-monitor/sigstore-config.yaml | 9 + configs/bt2x-server/config.yaml | 17 + configs/dex/dex.yaml | 55 + configs/sigstore/fulcio/config/cert-gen.sh | 21 + configs/sigstore/fulcio/config/config.json | 9 + .../config/dex/docker-compose-config.yaml | 55 + .../sigstore/fulcio/config/fulcio-config.yaml | 75 + configs/sigstore/fulcio/ctfe-init/Dockerfile | 25 + .../fulcio/ctfe-init/config/ct_server.cfg | 12 + configs/sigstore/fulcio/ctfe-init/logid.sh | 58 + configs/sigstore/rekor/key-gen.sh | 10 + configs/tuf/tuf-cli-config.yaml | 34 + docker-compose.bt4ot.yml | 291 +++ docker-compose.monitor.yml | 30 + docker-compose.sigstore.yml | 462 +++++ dockerfiles/bt2x-monitor.Dockerfile | 213 ++ dockerfiles/bt2x-ota.Dockerfile | 213 ++ dockerfiles/bt2x.Dockerfile | 213 ++ dockerfiles/oci-registry.Dockerfile | 206 ++ dockerfiles/populate-rekor.Dockerfile | 209 ++ generate-benchmark-data.py | 255 +++ images/development-setup.svg | 3 + images/docker-setup.svg | 3 + images/general-operation.svg | 3 + images/src/development-setup.drawio | 157 ++ images/src/docker-setup.drawio | 88 + images/src/general-operation.drawio | 296 +++ performance_benchmarks.py | 318 +++ pico-w-bootloader/.cargo/config.toml | 8 + pico-w-bootloader/.dockerignore | 16 + pico-w-bootloader/.gitignore | 2 + pico-w-bootloader/Cargo.toml | 33 + pico-w-bootloader/LICENSE-APACHE | 201 ++ pico-w-bootloader/LICENSE-CC-BY-SA | 428 ++++ pico-w-bootloader/LICENSE-MIT | 25 + pico-w-bootloader/README.md | 4 + pico-w-bootloader/build.rs | 28 + pico-w-bootloader/main.rs | 54 + pico-w-bootloader/memory.x | 31 + pico-w-bootloader/src/main.rs | 54 + populate-rekor.sh | 219 ++ process-benchmark-data.py | 86 + readme.md | 145 ++ setup.sh | 222 ++ tuf-no-std/.dockerignore | 16 + tuf-no-std/.gitignore | 8 + tuf-no-std/Cargo.toml | 42 + tuf-no-std/LICENSE.txt | 202 ++ tuf-no-std/README.md | 21 + tuf-no-std/deny.toml | 237 +++ tuf-no-std/test-scenarios.md | 77 + tuf-no-std/tests/1.registry.npmjs.org.json | 23 + tuf-no-std/tests/1.root.json | 130 ++ tuf-no-std/tests/100.snapshot.json | 64 + tuf-no-std/tests/101.snapshot.json | 64 + tuf-no-std/tests/102.snapshot.json | 64 + tuf-no-std/tests/103.snapshot.json | 64 + tuf-no-std/tests/104.snapshot.json | 64 + tuf-no-std/tests/105.snapshot.json | 64 + tuf-no-std/tests/2.root.json | 144 ++ tuf-no-std/tests/3.root.json | 136 ++ tuf-no-std/tests/4.root.json | 144 ++ tuf-no-std/tests/5.root.json | 156 ++ tuf-no-std/tests/5.targets.json | 125 ++ tuf-no-std/tests/53.snapshot.json | 56 + tuf-no-std/tests/54.snapshot.json | 56 + tuf-no-std/tests/55.snapshot.json | 56 + tuf-no-std/tests/56.snapshot.json | 56 + tuf-no-std/tests/57.snapshot.json | 56 + tuf-no-std/tests/58.snapshot.json | 56 + tuf-no-std/tests/59.snapshot.json | 56 + tuf-no-std/tests/6.root.json | 144 ++ tuf-no-std/tests/6.targets.json | 136 ++ tuf-no-std/tests/60.snapshot.json | 56 + tuf-no-std/tests/61.snapshot.json | 56 + tuf-no-std/tests/62.snapshot.json | 56 + tuf-no-std/tests/63.snapshot.json | 56 + tuf-no-std/tests/64.snapshot.json | 56 + tuf-no-std/tests/65.snapshot.json | 56 + tuf-no-std/tests/66.snapshot.json | 56 + tuf-no-std/tests/67.snapshot.json | 56 + tuf-no-std/tests/68.snapshot.json | 56 + tuf-no-std/tests/69.snapshot.json | 56 + tuf-no-std/tests/7.root.json | 140 ++ tuf-no-std/tests/7.targets.json | 160 ++ tuf-no-std/tests/70.snapshot.json | 56 + tuf-no-std/tests/71.snapshot.json | 56 + tuf-no-std/tests/72.snapshot.json | 56 + tuf-no-std/tests/73.snapshot.json | 56 + tuf-no-std/tests/74.snapshot.json | 56 + tuf-no-std/tests/75.snapshot.json | 56 + tuf-no-std/tests/76.snapshot.json | 56 + tuf-no-std/tests/77.snapshot.json | 56 + tuf-no-std/tests/78.snapshot.json | 56 + tuf-no-std/tests/79.snapshot.json | 64 + tuf-no-std/tests/80.snapshot.json | 64 + tuf-no-std/tests/81.snapshot.json | 64 + tuf-no-std/tests/82.snapshot.json | 64 + tuf-no-std/tests/83.snapshot.json | 64 + tuf-no-std/tests/84.snapshot.json | 64 + tuf-no-std/tests/85.snapshot.json | 64 + tuf-no-std/tests/86.snapshot.json | 64 + tuf-no-std/tests/87.snapshot.json | 64 + tuf-no-std/tests/88.snapshot.json | 64 + tuf-no-std/tests/89.snapshot.json | 64 + tuf-no-std/tests/90.snapshot.json | 64 + tuf-no-std/tests/91.snapshot.json | 64 + tuf-no-std/tests/92.snapshot.json | 64 + tuf-no-std/tests/93.snapshot.json | 64 + tuf-no-std/tests/94.snapshot.json | 64 + tuf-no-std/tests/95.snapshot.json | 64 + tuf-no-std/tests/96.snapshot.json | 64 + tuf-no-std/tests/97.snapshot.json | 64 + tuf-no-std/tests/98.snapshot.json | 64 + tuf-no-std/tests/99.snapshot.json | 64 + .../ed25519_metadata/root_with_ed25519.json | 71 + .../snapshot_with_ed25519.json | 19 + .../targets_with_ed25519.json | 15 + .../timestamp_with_ed25519.json | 19 + tuf-no-std/tests/registry.npmjs.org.json | 23 + tuf-no-std/tests/repository_data/README.md | 48 + .../tests/repository_data/client/map.json | 33 + .../metadata/current/1.root.json | 87 + .../metadata/current/role1.json | 49 + .../metadata/current/role2.json | 19 + .../metadata/current/root.json | 87 + .../metadata/current/snapshot.json | 25 + .../metadata/current/targets.json | 61 + .../metadata/current/timestamp.json | 23 + .../metadata/previous/1.root.json | 87 + .../metadata/previous/role1.json | 49 + .../metadata/previous/role2.json | 19 + .../metadata/previous/root.json | 87 + .../metadata/previous/snapshot.json | 25 + .../metadata/previous/targets.json | 61 + .../metadata/previous/timestamp.json | 23 + .../metadata/current/1.root.json | 87 + .../metadata/current/role1.json | 49 + .../metadata/current/role2.json | 19 + .../metadata/current/root.json | 87 + .../metadata/current/snapshot.json | 25 + .../metadata/current/targets.json | 61 + .../metadata/current/timestamp.json | 23 + .../metadata/previous/1.root.json | 87 + .../metadata/previous/role1.json | 49 + .../metadata/previous/role2.json | 19 + .../metadata/previous/root.json | 87 + .../metadata/previous/snapshot.json | 25 + .../metadata/previous/targets.json | 61 + .../metadata/previous/timestamp.json | 23 + .../repository_data/fishy_rolenames/1.a.json | 15 + .../fishy_rolenames/metadata/1...json | 15 + .../fishy_rolenames/metadata/1.root.json | 71 + .../fishy_rolenames/metadata/1.targets.json | 75 + .../fishy_rolenames/metadata/1.\303\266.json" | 15 + .../fishy_rolenames/metadata/2.snapshot.json | 28 + .../fishy_rolenames/metadata/timestamp.json | 19 + .../repository_data/keystore/delegation_key | 1 + .../keystore/delegation_key.pub | 1 + .../tests/repository_data/keystore/root_key | 42 + .../repository_data/keystore/root_key.pub | 11 + .../tests/repository_data/keystore/root_key2 | 1 + .../repository_data/keystore/root_key2.pub | 1 + .../tests/repository_data/keystore/root_key3 | 1 + .../repository_data/keystore/root_key3.pub | 1 + .../repository_data/keystore/snapshot_key | 1 + .../repository_data/keystore/snapshot_key.pub | 1 + .../repository_data/keystore/targets_key | 1 + .../repository_data/keystore/targets_key.pub | 1 + .../repository_data/keystore/timestamp_key | 1 + .../keystore/timestamp_key.pub | 1 + tuf-no-std/tests/repository_data/map.json | 29 + .../repository_data/project/targets/file1.txt | 1 + .../repository_data/project/targets/file2.txt | 1 + .../repository_data/project/targets/file3.txt | 1 + .../project/test-flat/project.cfg | 1 + .../project/test-flat/role1.json | 19 + .../project/test-flat/test-flat.json | 58 + .../repository/metadata.staged/1.root.json | 87 + .../repository/metadata.staged/role1.json | 49 + .../repository/metadata.staged/role2.json | 19 + .../repository/metadata.staged/root.json | 87 + .../repository/metadata.staged/snapshot.json | 25 + .../repository/metadata.staged/targets.json | 61 + .../repository/metadata.staged/timestamp.json | 23 + .../repository/metadata/1.root.json | 87 + .../repository/metadata/role1.json | 49 + .../repository/metadata/role2.json | 15 + .../repository/metadata/root.json | 87 + .../repository/metadata/snapshot.json | 25 + .../repository/metadata/targets.json | 61 + .../repository/metadata/timestamp.json | 23 + .../repository/targets/file1.txt | 1 + .../repository/targets/file2.txt | 1 + .../repository/targets/file3.txt | 1 + tuf-no-std/tests/root.json | 71 + tuf-no-std/tests/rsa-root.json | 40 + tuf-no-std/tests/sigstore-root.json | 156 ++ tuf-no-std/tests/snapshot.json | 64 + tuf-no-std/tests/snapshots.json | 32 + tuf-no-std/tests/targets.json | 160 ++ tuf-no-std/tests/timestamp.json | 24 + tuf-no-std/tuf-no-std-cli/Cargo.toml | 23 + tuf-no-std/tuf-no-std-cli/src/args.rs | 8 + tuf-no-std/tuf-no-std-cli/src/config.rs | 38 + tuf-no-std/tuf-no-std-cli/src/main.rs | 141 ++ tuf-no-std/tuf-no-std-common/Cargo.toml | 63 + tuf-no-std/tuf-no-std-common/src/common.rs | 1 + tuf-no-std/tuf-no-std-common/src/constants.rs | 15 + .../src/crypto/composite_spki.rs | 116 ++ .../tuf-no-std-common/src/crypto/mod.rs | 55 + .../tuf-no-std-common/src/crypto/sign.rs | 100 + tuf-no-std/tuf-no-std-common/src/error.rs | 42 + tuf-no-std/tuf-no-std-common/src/lib.rs | 62 + tuf-no-std/tuf-no-std-common/src/remote.rs | 54 + tuf-no-std/tuf-no-std-common/src/storage.rs | 193 ++ tuf-no-std/tuf-no-std-der/Cargo.toml | 61 + tuf-no-std/tuf-no-std-der/src/lib.rs | 452 ++++ tuf-no-std/tuf-no-std-der/src/role.rs | 17 + tuf-no-std/tuf-no-std-der/src/root.rs | 63 + tuf-no-std/tuf-no-std-der/src/signature.rs | 3 + tuf-no-std/tuf-no-std-der/src/snapshot.rs | 49 + tuf-no-std/tuf-no-std-der/src/targets.rs | 88 + tuf-no-std/tuf-no-std-der/src/timestamp.rs | 26 + tuf-no-std/tuf-no-std/Cargo.toml | 102 + tuf-no-std/tuf-no-std/src/builder.rs | 363 ++++ tuf-no-std/tuf-no-std/src/canonical.rs | 12 + tuf-no-std/tuf-no-std/src/format.rs | 32 + tuf-no-std/tuf-no-std/src/lib.rs | 191 ++ tuf-no-std/tuf-no-std/src/role/mod.rs | 167 ++ tuf-no-std/tuf-no-std/src/role/root.rs | 1050 ++++++++++ tuf-no-std/tuf-no-std/src/role/snapshot.rs | 843 ++++++++ tuf-no-std/tuf-no-std/src/role/targets.rs | 423 ++++ tuf-no-std/tuf-no-std/src/role/timestamp.rs | 544 +++++ tuf-no-std/tuf-no-std/src/signature.rs | 18 + tuf-no-std/tuf-no-std/src/utils.rs | 382 ++++ .../tuf-no-std/tests/integration_tests.rs | 282 +++ 520 files changed, 36319 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 LICENSE.txt create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:35:36.205608.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:36:34.252069.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:37:32.228433.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:38:31.502479.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:39:29.665462.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:40:27.522857.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:41:24.866985.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:42:22.826663.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:43:21.808797.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:44:19.972532.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:45:18.033419.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:46:16.094788.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:47:14.155796.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:48:12.217400.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:49:10.278672.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:50:08.440536.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:51:06.503707.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:52:04.667937.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:02.554193.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:59.971332.json create mode 100644 benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:54:58.135862.json create mode 100644 benchmark-output/benchmarks-size.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:15:36.214852.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:16:39.421518.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:17:33.877914.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:18:27.641227.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:19:22.223523.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:22:22.761514.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:23:17.150873.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:24:12.539264.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:25:08.425179.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:26:04.337105.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:27:00.148576.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:27:55.237711.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:28:50.945571.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:29:46.754391.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:30:40.412155.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:31:35.143103.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:32:28.985440.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:33:22.107728.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:34:16.072524.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:36:24.696037.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:37:17.733229.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:38:10.775495.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:39:03.822099.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 07:39:56.865710.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:28:50.463366.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:29:42.892983.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:30:36.962670.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:31:31.136874.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:32:24.280363.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:34:02.378899.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:34:55.625728.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:35:48.872563.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:36:43.145201.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:37:36.392582.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:38:29.743328.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:39:24.220492.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:40:18.185840.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:41:12.867675.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:42:07.437976.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:43:02.032717.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:43:55.949029.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:44:49.855015.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:45:44.434503.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:46:39.117894.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:47:34.209877.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:48:28.789175.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:49:24.085695.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:50:19.382249.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:51:14.650965.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:52:10.182122.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:53:04.763449.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:54:00.160700.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:54:54.841759.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:55:50.241128.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:56:45.536299.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:57:40.118298.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:58:35.186132.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 08:59:31.019219.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:00:27.237984.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:01:23.149472.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:02:19.674217.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:03:24.598129.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:04:21.226169.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:05:17.137034.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:06:13.048665.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:07:09.061002.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:08:05.015132.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:09:00.881587.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:09:57.487831.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:10:54.033808.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:11:50.558019.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:12:47.697540.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:13:44.324380.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:14:40.746971.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:15:38.176186.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:16:35.334034.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:17:31.844067.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:18:28.999147.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:19:25.625981.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:20:31.062208.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:21:28.611003.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:22:25.340802.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:23:22.684988.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:24:20.949997.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:25:17.577855.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:26:15.844016.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:27:12.474049.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:28:09.714267.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:29:06.342918.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:30:02.885976.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:31:00.110025.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:31:57.370331.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:32:55.619965.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:33:52.964818.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:34:50.308819.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:35:47.567040.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:36:44.081613.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:37:41.421666.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:38:38.049922.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:39:35.366876.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:40:32.950956.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:41:29.679246.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:42:27.020558.json create mode 100644 benchmark-output/performance-benchmark-2024-08-20 09:43:24.365362.json create mode 100644 benchmarking/.gitignore create mode 100644 benchmarking/benchmark_server.py create mode 100644 benchmarking/performance-benchmarks.ipynb create mode 100644 benchmarking/requirements.txt create mode 100644 benchmarking/size-benchmarks.ipynb create mode 100644 bt2x-embassy-std/.dockerignore create mode 100644 bt2x-embassy-std/Cargo.lock create mode 100644 bt2x-embassy-std/Cargo.toml create mode 100644 bt2x-embassy-std/LICENSE.txt create mode 100644 bt2x-embassy-std/deny.toml create mode 100644 bt2x-embassy-std/readme.md create mode 100644 bt2x-embassy-std/src/main.rs create mode 100644 bt2x-embedded-ffi/.cargo/config.toml create mode 100644 bt2x-embedded-ffi/.dockerignore create mode 100644 bt2x-embedded-ffi/.gitignore create mode 100644 bt2x-embedded-ffi/Cargo.toml create mode 100644 bt2x-embedded-ffi/LICENSE.txt create mode 100644 bt2x-embedded-ffi/bindings.h create mode 100644 bt2x-embedded-ffi/build.rs create mode 100644 bt2x-embedded-ffi/deny.toml create mode 100644 bt2x-embedded-ffi/rust-toolchain.toml create mode 100644 bt2x-embedded-ffi/src/lib.rs create mode 100644 bt2x-pi-pico-w/.cargo/config.toml create mode 100644 bt2x-pi-pico-w/.dockerignore create mode 100644 bt2x-pi-pico-w/.gitignore create mode 100644 bt2x-pi-pico-w/Cargo.toml create mode 100644 bt2x-pi-pico-w/LICENSE.txt create mode 100644 bt2x-pi-pico-w/README.md create mode 100644 bt2x-pi-pico-w/build.rs create mode 100644 bt2x-pi-pico-w/deny.toml create mode 100755 bt2x-pi-pico-w/firmware/43439A0.bin create mode 100755 bt2x-pi-pico-w/firmware/43439A0_clm.bin create mode 100644 bt2x-pi-pico-w/firmware/LICENSE-permissive-binary-license-1.0.txt create mode 100644 bt2x-pi-pico-w/firmware/README.md create mode 100644 bt2x-pi-pico-w/memory.x create mode 100644 bt2x-pi-pico-w/random-bytes create mode 100644 bt2x-pi-pico-w/rust-toolchain.toml create mode 100644 bt2x-pi-pico-w/src/benchmarking/code-size-full.rs create mode 100644 bt2x-pi-pico-w/src/benchmarking/code-size-no-tuf.rs create mode 100644 bt2x-pi-pico-w/src/benchmarking/code-size-simple-signature.rs create mode 100644 bt2x-pi-pico-w/src/benchmarking/code-size-update-only.rs create mode 100644 bt2x-pi-pico-w/src/benchmarking/performance.rs create mode 100644 bt2x-pi-pico-w/src/blinky.rs create mode 100644 bt2x-pi-pico-w/src/main.rs create mode 100644 bt2x-pi-pico/.cargo/config.toml create mode 100644 bt2x-pi-pico/.dockerignore create mode 100644 bt2x-pi-pico/.github/workflows/ci_checks.yml create mode 100644 bt2x-pi-pico/.gitignore create mode 100644 bt2x-pi-pico/.vscode/launch.json create mode 100644 bt2x-pi-pico/.vscode/settings.json create mode 100644 bt2x-pi-pico/Cargo.toml create mode 100644 bt2x-pi-pico/Embed.toml create mode 100644 bt2x-pi-pico/README.md create mode 100644 bt2x-pi-pico/build.rs create mode 100644 bt2x-pi-pico/debug_probes.md create mode 100644 bt2x-pi-pico/memory.x create mode 100644 bt2x-pi-pico/rust-toolchain.toml create mode 100644 bt2x-pi-pico/src/main.rs create mode 100644 bt2x/.dockerignore create mode 100644 bt2x/.gitignore create mode 100644 bt2x/Cargo.toml create mode 100644 bt2x/LICENSE.txt create mode 100644 bt2x/README.md create mode 100644 bt2x/bt2x-cli/Cargo.toml create mode 100644 bt2x/bt2x-cli/src/main.rs create mode 100644 bt2x/bt2x-common/Cargo.toml create mode 100644 bt2x/bt2x-common/src/artifact/mod.rs create mode 100644 bt2x/bt2x-common/src/artifact/source/bundle.rs create mode 100644 bt2x/bt2x-common/src/artifact/source/mod.rs create mode 100644 bt2x/bt2x-common/src/artifact/source/oci.rs create mode 100644 bt2x/bt2x-common/src/error.rs create mode 100644 bt2x/bt2x-common/src/gossip.rs create mode 100644 bt2x/bt2x-common/src/lib.rs create mode 100644 bt2x/bt2x-common/src/merkle/mod.rs create mode 100644 bt2x/bt2x-common/src/merkle/rfc6962.rs create mode 100644 bt2x/bt2x-common/src/merkle/tree.rs create mode 100644 bt2x/bt2x-common/src/merkle/verify.rs create mode 100644 bt2x/bt2x-common/src/rekor.rs create mode 100644 bt2x/bt2x-common/src/sct.rs create mode 100644 bt2x/bt2x-common/src/serde.rs create mode 100644 bt2x/bt2x-common/src/sigstore_config.rs create mode 100644 bt2x/bt2x-common/src/tuf/mod.rs create mode 100644 bt2x/bt2x-common/src/verifier/bt.rs create mode 100644 bt2x/bt2x-common/src/verifier/mod.rs create mode 100644 bt2x/bt2x-embedded/.gitignore create mode 100644 bt2x/bt2x-embedded/Cargo.toml create mode 100644 bt2x/bt2x-embedded/src/bundle/mod.rs create mode 100644 bt2x/bt2x-embedded/src/lib.rs create mode 100644 bt2x/bt2x-embedded/src/models/alpine.rs create mode 100644 bt2x/bt2x-embedded/src/models/alpine_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/consistency_proof.rs create mode 100644 bt2x/bt2x-embedded/src/models/cose.rs create mode 100644 bt2x/bt2x-embedded/src/models/cose_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/error.rs create mode 100644 bt2x/bt2x-embedded/src/models/hashedrekord.rs create mode 100644 bt2x/bt2x-embedded/src/models/hashedrekord_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/helm.rs create mode 100644 bt2x/bt2x-embedded/src/models/helm_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/inactive_shard_log_info.rs create mode 100644 bt2x/bt2x-embedded/src/models/inclusion_proof.rs create mode 100644 bt2x/bt2x-embedded/src/models/intoto.rs create mode 100644 bt2x/bt2x-embedded/src/models/intoto_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/jar.rs create mode 100644 bt2x/bt2x-embedded/src/models/jar_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/log_entry_value.rs create mode 100644 bt2x/bt2x-embedded/src/models/log_entry_value_attestation.rs create mode 100644 bt2x/bt2x-embedded/src/models/log_entry_value_verification.rs create mode 100644 bt2x/bt2x-embedded/src/models/log_info.rs create mode 100644 bt2x/bt2x-embedded/src/models/mod.rs create mode 100644 bt2x/bt2x-embedded/src/models/proposed_entry.rs create mode 100644 bt2x/bt2x-embedded/src/models/rekord.rs create mode 100644 bt2x/bt2x-embedded/src/models/rekord_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/rfc3161.rs create mode 100644 bt2x/bt2x-embedded/src/models/rfc3161_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/rpm.rs create mode 100644 bt2x/bt2x-embedded/src/models/rpm_all_of.rs create mode 100644 bt2x/bt2x-embedded/src/models/schema/alpine_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/cose_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/hashedrekord_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/helm_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/intoto_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/jar_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/rekord_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/rfc3161_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/rpm_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/schema/tuf_v0_0_1_schema.json create mode 100644 bt2x/bt2x-embedded/src/models/search_index.rs create mode 100644 bt2x/bt2x-embedded/src/models/search_index_public_key.rs create mode 100644 bt2x/bt2x-embedded/src/models/search_log_query.rs create mode 100644 bt2x/bt2x-embedded/src/models/tuf.rs create mode 100644 bt2x/bt2x-embedded/src/models/tuf_all_of.rs create mode 100644 bt2x/bt2x-flasher/Cargo.toml create mode 100644 bt2x/bt2x-flasher/src/args.rs create mode 100644 bt2x/bt2x-flasher/src/main.rs create mode 100644 bt2x/bt2x-monitor/Cargo.toml create mode 100644 bt2x/bt2x-monitor/README.md create mode 100644 bt2x/bt2x-monitor/src/cli.rs create mode 100644 bt2x/bt2x-monitor/src/lib.rs create mode 100644 bt2x/bt2x-monitor/src/main.rs create mode 100644 bt2x/bt2x-ota-common/Cargo.toml create mode 100644 bt2x/bt2x-ota-common/src/flash.rs create mode 100644 bt2x/bt2x-ota-common/src/lib.rs create mode 100644 bt2x/bt2x-ota-common/src/net.rs create mode 100644 bt2x/bt2x-ota/Cargo.toml create mode 100644 bt2x/bt2x-ota/src/cli.rs create mode 100644 bt2x/bt2x-ota/src/main.rs create mode 100644 bt2x/bt2x-server/Cargo.toml create mode 100644 bt2x/bt2x-server/src/cli.rs create mode 100644 bt2x/bt2x-server/src/config.rs create mode 100644 bt2x/bt2x-server/src/main.rs create mode 100644 bt2x/deny.toml create mode 100644 bt2x/rust-toolchain.toml create mode 100755 build-docs.sh create mode 100644 configs/bt2x-monitor/sigstore-config.yaml create mode 100644 configs/bt2x-server/config.yaml create mode 100644 configs/dex/dex.yaml create mode 100755 configs/sigstore/fulcio/config/cert-gen.sh create mode 100644 configs/sigstore/fulcio/config/config.json create mode 100644 configs/sigstore/fulcio/config/dex/docker-compose-config.yaml create mode 100644 configs/sigstore/fulcio/config/fulcio-config.yaml create mode 100644 configs/sigstore/fulcio/ctfe-init/Dockerfile create mode 100644 configs/sigstore/fulcio/ctfe-init/config/ct_server.cfg create mode 100644 configs/sigstore/fulcio/ctfe-init/logid.sh create mode 100755 configs/sigstore/rekor/key-gen.sh create mode 100644 configs/tuf/tuf-cli-config.yaml create mode 100644 docker-compose.bt4ot.yml create mode 100644 docker-compose.monitor.yml create mode 100644 docker-compose.sigstore.yml create mode 100644 dockerfiles/bt2x-monitor.Dockerfile create mode 100644 dockerfiles/bt2x-ota.Dockerfile create mode 100644 dockerfiles/bt2x.Dockerfile create mode 100644 dockerfiles/oci-registry.Dockerfile create mode 100644 dockerfiles/populate-rekor.Dockerfile create mode 100644 generate-benchmark-data.py create mode 100644 images/development-setup.svg create mode 100644 images/docker-setup.svg create mode 100644 images/general-operation.svg create mode 100644 images/src/development-setup.drawio create mode 100644 images/src/docker-setup.drawio create mode 100644 images/src/general-operation.drawio create mode 100644 performance_benchmarks.py create mode 100644 pico-w-bootloader/.cargo/config.toml create mode 100644 pico-w-bootloader/.dockerignore create mode 100644 pico-w-bootloader/.gitignore create mode 100644 pico-w-bootloader/Cargo.toml create mode 100644 pico-w-bootloader/LICENSE-APACHE create mode 100644 pico-w-bootloader/LICENSE-CC-BY-SA create mode 100644 pico-w-bootloader/LICENSE-MIT create mode 100644 pico-w-bootloader/README.md create mode 100644 pico-w-bootloader/build.rs create mode 100644 pico-w-bootloader/main.rs create mode 100644 pico-w-bootloader/memory.x create mode 100644 pico-w-bootloader/src/main.rs create mode 100755 populate-rekor.sh create mode 100644 process-benchmark-data.py create mode 100644 readme.md create mode 100755 setup.sh create mode 100644 tuf-no-std/.dockerignore create mode 100755 tuf-no-std/.gitignore create mode 100644 tuf-no-std/Cargo.toml create mode 100644 tuf-no-std/LICENSE.txt create mode 100644 tuf-no-std/README.md create mode 100644 tuf-no-std/deny.toml create mode 100644 tuf-no-std/test-scenarios.md create mode 100644 tuf-no-std/tests/1.registry.npmjs.org.json create mode 100644 tuf-no-std/tests/1.root.json create mode 100644 tuf-no-std/tests/100.snapshot.json create mode 100644 tuf-no-std/tests/101.snapshot.json create mode 100644 tuf-no-std/tests/102.snapshot.json create mode 100644 tuf-no-std/tests/103.snapshot.json create mode 100644 tuf-no-std/tests/104.snapshot.json create mode 100644 tuf-no-std/tests/105.snapshot.json create mode 100644 tuf-no-std/tests/2.root.json create mode 100644 tuf-no-std/tests/3.root.json create mode 100644 tuf-no-std/tests/4.root.json create mode 100644 tuf-no-std/tests/5.root.json create mode 100644 tuf-no-std/tests/5.targets.json create mode 100644 tuf-no-std/tests/53.snapshot.json create mode 100644 tuf-no-std/tests/54.snapshot.json create mode 100644 tuf-no-std/tests/55.snapshot.json create mode 100644 tuf-no-std/tests/56.snapshot.json create mode 100644 tuf-no-std/tests/57.snapshot.json create mode 100644 tuf-no-std/tests/58.snapshot.json create mode 100644 tuf-no-std/tests/59.snapshot.json create mode 100644 tuf-no-std/tests/6.root.json create mode 100644 tuf-no-std/tests/6.targets.json create mode 100644 tuf-no-std/tests/60.snapshot.json create mode 100644 tuf-no-std/tests/61.snapshot.json create mode 100644 tuf-no-std/tests/62.snapshot.json create mode 100644 tuf-no-std/tests/63.snapshot.json create mode 100644 tuf-no-std/tests/64.snapshot.json create mode 100644 tuf-no-std/tests/65.snapshot.json create mode 100644 tuf-no-std/tests/66.snapshot.json create mode 100644 tuf-no-std/tests/67.snapshot.json create mode 100644 tuf-no-std/tests/68.snapshot.json create mode 100644 tuf-no-std/tests/69.snapshot.json create mode 100644 tuf-no-std/tests/7.root.json create mode 100644 tuf-no-std/tests/7.targets.json create mode 100644 tuf-no-std/tests/70.snapshot.json create mode 100644 tuf-no-std/tests/71.snapshot.json create mode 100644 tuf-no-std/tests/72.snapshot.json create mode 100644 tuf-no-std/tests/73.snapshot.json create mode 100644 tuf-no-std/tests/74.snapshot.json create mode 100644 tuf-no-std/tests/75.snapshot.json create mode 100644 tuf-no-std/tests/76.snapshot.json create mode 100644 tuf-no-std/tests/77.snapshot.json create mode 100644 tuf-no-std/tests/78.snapshot.json create mode 100644 tuf-no-std/tests/79.snapshot.json create mode 100644 tuf-no-std/tests/80.snapshot.json create mode 100644 tuf-no-std/tests/81.snapshot.json create mode 100644 tuf-no-std/tests/82.snapshot.json create mode 100644 tuf-no-std/tests/83.snapshot.json create mode 100644 tuf-no-std/tests/84.snapshot.json create mode 100644 tuf-no-std/tests/85.snapshot.json create mode 100644 tuf-no-std/tests/86.snapshot.json create mode 100644 tuf-no-std/tests/87.snapshot.json create mode 100644 tuf-no-std/tests/88.snapshot.json create mode 100644 tuf-no-std/tests/89.snapshot.json create mode 100644 tuf-no-std/tests/90.snapshot.json create mode 100644 tuf-no-std/tests/91.snapshot.json create mode 100644 tuf-no-std/tests/92.snapshot.json create mode 100644 tuf-no-std/tests/93.snapshot.json create mode 100644 tuf-no-std/tests/94.snapshot.json create mode 100644 tuf-no-std/tests/95.snapshot.json create mode 100644 tuf-no-std/tests/96.snapshot.json create mode 100644 tuf-no-std/tests/97.snapshot.json create mode 100644 tuf-no-std/tests/98.snapshot.json create mode 100644 tuf-no-std/tests/99.snapshot.json create mode 100644 tuf-no-std/tests/generated_data/ed25519_metadata/root_with_ed25519.json create mode 100644 tuf-no-std/tests/generated_data/ed25519_metadata/snapshot_with_ed25519.json create mode 100644 tuf-no-std/tests/generated_data/ed25519_metadata/targets_with_ed25519.json create mode 100644 tuf-no-std/tests/generated_data/ed25519_metadata/timestamp_with_ed25519.json create mode 100644 tuf-no-std/tests/registry.npmjs.org.json create mode 100644 tuf-no-std/tests/repository_data/README.md create mode 100644 tuf-no-std/tests/repository_data/client/map.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/1.root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role1.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role2.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/snapshot.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/targets.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/timestamp.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/1.root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role1.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role2.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/snapshot.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/targets.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/timestamp.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/1.root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role1.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role2.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/snapshot.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/targets.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/timestamp.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/1.root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role1.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role2.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/root.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/snapshot.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/targets.json create mode 100644 tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/timestamp.json create mode 100644 tuf-no-std/tests/repository_data/fishy_rolenames/1.a.json create mode 100644 tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1...json create mode 100644 tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.root.json create mode 100644 tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.targets.json create mode 100644 "tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.\303\266.json" create mode 100644 tuf-no-std/tests/repository_data/fishy_rolenames/metadata/2.snapshot.json create mode 100644 tuf-no-std/tests/repository_data/fishy_rolenames/metadata/timestamp.json create mode 100644 tuf-no-std/tests/repository_data/keystore/delegation_key create mode 100644 tuf-no-std/tests/repository_data/keystore/delegation_key.pub create mode 100644 tuf-no-std/tests/repository_data/keystore/root_key create mode 100644 tuf-no-std/tests/repository_data/keystore/root_key.pub create mode 100644 tuf-no-std/tests/repository_data/keystore/root_key2 create mode 100644 tuf-no-std/tests/repository_data/keystore/root_key2.pub create mode 100644 tuf-no-std/tests/repository_data/keystore/root_key3 create mode 100644 tuf-no-std/tests/repository_data/keystore/root_key3.pub create mode 100644 tuf-no-std/tests/repository_data/keystore/snapshot_key create mode 100644 tuf-no-std/tests/repository_data/keystore/snapshot_key.pub create mode 100644 tuf-no-std/tests/repository_data/keystore/targets_key create mode 100644 tuf-no-std/tests/repository_data/keystore/targets_key.pub create mode 100644 tuf-no-std/tests/repository_data/keystore/timestamp_key create mode 100644 tuf-no-std/tests/repository_data/keystore/timestamp_key.pub create mode 100644 tuf-no-std/tests/repository_data/map.json create mode 100644 tuf-no-std/tests/repository_data/project/targets/file1.txt create mode 100644 tuf-no-std/tests/repository_data/project/targets/file2.txt create mode 100644 tuf-no-std/tests/repository_data/project/targets/file3.txt create mode 100644 tuf-no-std/tests/repository_data/project/test-flat/project.cfg create mode 100644 tuf-no-std/tests/repository_data/project/test-flat/role1.json create mode 100644 tuf-no-std/tests/repository_data/project/test-flat/test-flat.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata.staged/1.root.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata.staged/role1.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata.staged/role2.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata.staged/root.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata.staged/snapshot.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata.staged/targets.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata.staged/timestamp.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata/1.root.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata/role1.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata/role2.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata/root.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata/snapshot.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata/targets.json create mode 100644 tuf-no-std/tests/repository_data/repository/metadata/timestamp.json create mode 100644 tuf-no-std/tests/repository_data/repository/targets/file1.txt create mode 100644 tuf-no-std/tests/repository_data/repository/targets/file2.txt create mode 100644 tuf-no-std/tests/repository_data/repository/targets/file3.txt create mode 100755 tuf-no-std/tests/root.json create mode 100644 tuf-no-std/tests/rsa-root.json create mode 100755 tuf-no-std/tests/sigstore-root.json create mode 100644 tuf-no-std/tests/snapshot.json create mode 100755 tuf-no-std/tests/snapshots.json create mode 100755 tuf-no-std/tests/targets.json create mode 100755 tuf-no-std/tests/timestamp.json create mode 100644 tuf-no-std/tuf-no-std-cli/Cargo.toml create mode 100644 tuf-no-std/tuf-no-std-cli/src/args.rs create mode 100644 tuf-no-std/tuf-no-std-cli/src/config.rs create mode 100644 tuf-no-std/tuf-no-std-cli/src/main.rs create mode 100755 tuf-no-std/tuf-no-std-common/Cargo.toml create mode 100644 tuf-no-std/tuf-no-std-common/src/common.rs create mode 100644 tuf-no-std/tuf-no-std-common/src/constants.rs create mode 100644 tuf-no-std/tuf-no-std-common/src/crypto/composite_spki.rs create mode 100644 tuf-no-std/tuf-no-std-common/src/crypto/mod.rs create mode 100644 tuf-no-std/tuf-no-std-common/src/crypto/sign.rs create mode 100644 tuf-no-std/tuf-no-std-common/src/error.rs create mode 100755 tuf-no-std/tuf-no-std-common/src/lib.rs create mode 100644 tuf-no-std/tuf-no-std-common/src/remote.rs create mode 100644 tuf-no-std/tuf-no-std-common/src/storage.rs create mode 100644 tuf-no-std/tuf-no-std-der/Cargo.toml create mode 100644 tuf-no-std/tuf-no-std-der/src/lib.rs create mode 100644 tuf-no-std/tuf-no-std-der/src/role.rs create mode 100644 tuf-no-std/tuf-no-std-der/src/root.rs create mode 100644 tuf-no-std/tuf-no-std-der/src/signature.rs create mode 100644 tuf-no-std/tuf-no-std-der/src/snapshot.rs create mode 100644 tuf-no-std/tuf-no-std-der/src/targets.rs create mode 100644 tuf-no-std/tuf-no-std-der/src/timestamp.rs create mode 100644 tuf-no-std/tuf-no-std/Cargo.toml create mode 100644 tuf-no-std/tuf-no-std/src/builder.rs create mode 100644 tuf-no-std/tuf-no-std/src/canonical.rs create mode 100644 tuf-no-std/tuf-no-std/src/format.rs create mode 100644 tuf-no-std/tuf-no-std/src/lib.rs create mode 100644 tuf-no-std/tuf-no-std/src/role/mod.rs create mode 100644 tuf-no-std/tuf-no-std/src/role/root.rs create mode 100644 tuf-no-std/tuf-no-std/src/role/snapshot.rs create mode 100644 tuf-no-std/tuf-no-std/src/role/targets.rs create mode 100644 tuf-no-std/tuf-no-std/src/role/timestamp.rs create mode 100644 tuf-no-std/tuf-no-std/src/signature.rs create mode 100644 tuf-no-std/tuf-no-std/src/utils.rs create mode 100644 tuf-no-std/tuf-no-std/tests/integration_tests.rs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..81672ab --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +bt2x/target +tuf-no-std/target +bt2x-embassy-std/target +bt2x-pi-pico/target +bt2x-pi-pico-w/target +bt2x-embedded-ffi/target + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### OTHERS +.env +dockerfiles +benchmark-output +__pycache__ +builds + +**/target/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5874fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +.env +.direnv +*.log +.DS_Store + +bt2x/out +*.pem +bt2x-pi-pico-w/target/ + +.idea + +builds +bt2x/bt2x-embedded/dhat-heap.json + +out/* +build/* +bt2x-embassy-std/flash-test +bt2x-embassy-std/target/** +benchmark-output/old +benchmark-output/big-bin +benchmark-output/small-bin + +__pycache__ + +# drawio +**/*.bkp + +**/bundle.json +**/*cdx.json +**/*cdx.xml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..91019be --- /dev/null +++ b/.gitmodules @@ -0,0 +1,2 @@ +[submodule "sigstore-rs/"] + branch = development diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..4052ad4 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:35:36.205608.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:35:36.205608.json new file mode 100644 index 0000000..5889b0a --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:35:36.205608.json @@ -0,0 +1 @@ +{"start_time":4465966,"end_time":20834022,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":102425000},"update_repo":{"secs":4,"nanos":523183000},"fetch_signature":{"secs":0,"nanos":22865000},"fetch_binary":{"secs":3,"nanos":164874000},"verify_binary":{"secs":2,"nanos":917344000},"verify_binary_naive":{"secs":0,"nanos":959270000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:36:34.252069.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:36:34.252069.json new file mode 100644 index 0000000..2151401 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:36:34.252069.json @@ -0,0 +1 @@ +{"start_time":4444768,"end_time":20806117,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84317000},"update_repo":{"secs":3,"nanos":807983000},"fetch_signature":{"secs":0,"nanos":22856000},"fetch_binary":{"secs":3,"nanos":917526000},"verify_binary":{"secs":2,"nanos":917303000},"verify_binary_naive":{"secs":0,"nanos":959269000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:37:32.228433.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:37:32.228433.json new file mode 100644 index 0000000..d48560e --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:37:32.228433.json @@ -0,0 +1 @@ +{"start_time":4402070,"end_time":20845265,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":83086000},"update_repo":{"secs":3,"nanos":807324000},"fetch_signature":{"secs":0,"nanos":21826000},"fetch_binary":{"secs":3,"nanos":982387000},"verify_binary":{"secs":2,"nanos":917252000},"verify_binary_naive":{"secs":0,"nanos":959313000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:38:31.502479.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:38:31.502479.json new file mode 100644 index 0000000..d537860 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:38:31.502479.json @@ -0,0 +1 @@ +{"start_time":4390670,"end_time":22059284,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":88121000},"update_repo":{"secs":5,"nanos":37039000},"fetch_signature":{"secs":0,"nanos":24942000},"fetch_binary":{"secs":3,"nanos":970556000},"verify_binary":{"secs":2,"nanos":917266000},"verify_binary_naive":{"secs":0,"nanos":959299000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:39:29.665462.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:39:29.665462.json new file mode 100644 index 0000000..0d60f70 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:39:29.665462.json @@ -0,0 +1 @@ +{"start_time":4427670,"end_time":20984372,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84520000},"update_repo":{"secs":3,"nanos":807121000},"fetch_signature":{"secs":0,"nanos":24121000},"fetch_binary":{"secs":4,"nanos":101730000},"verify_binary":{"secs":2,"nanos":917237000},"verify_binary_naive":{"secs":0,"nanos":959305000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:40:27.522857.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:40:27.522857.json new file mode 100644 index 0000000..cdf84a5 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:40:27.522857.json @@ -0,0 +1 @@ +{"start_time":4456773,"end_time":20714163,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":91508000},"update_repo":{"secs":3,"nanos":807737000},"fetch_signature":{"secs":0,"nanos":26469000},"fetch_binary":{"secs":3,"nanos":815230000},"verify_binary":{"secs":2,"nanos":917236000},"verify_binary_naive":{"secs":0,"nanos":959288000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:41:24.866985.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:41:24.866985.json new file mode 100644 index 0000000..a8651fb --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:41:24.866985.json @@ -0,0 +1 @@ +{"start_time":4426389,"end_time":20160179,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":80266000},"update_repo":{"secs":3,"nanos":810441000},"fetch_signature":{"secs":0,"nanos":24482000},"fetch_binary":{"secs":3,"nanos":296001000},"verify_binary":{"secs":2,"nanos":917296000},"verify_binary_naive":{"secs":0,"nanos":959284000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:42:22.826663.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:42:22.826663.json new file mode 100644 index 0000000..9a30d2e --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:42:22.826663.json @@ -0,0 +1 @@ +{"start_time":4412553,"end_time":20839687,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":119372000},"update_repo":{"secs":3,"nanos":776809000},"fetch_signature":{"secs":0,"nanos":25394000},"fetch_binary":{"secs":3,"nanos":985124000},"verify_binary":{"secs":2,"nanos":917301000},"verify_binary_naive":{"secs":0,"nanos":959282000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:43:21.808797.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:43:21.808797.json new file mode 100644 index 0000000..a208bc6 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:43:21.808797.json @@ -0,0 +1 @@ +{"start_time":4397345,"end_time":21762577,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":82187000},"update_repo":{"secs":4,"nanos":833429000},"fetch_signature":{"secs":0,"nanos":23958000},"fetch_binary":{"secs":3,"nanos":886587000},"verify_binary":{"secs":2,"nanos":917324000},"verify_binary_naive":{"secs":0,"nanos":959327000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:44:19.972532.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:44:19.972532.json new file mode 100644 index 0000000..b5f6071 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:44:19.972532.json @@ -0,0 +1 @@ +{"start_time":4481476,"end_time":20918107,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":80303000},"update_repo":{"secs":3,"nanos":811410000},"fetch_signature":{"secs":0,"nanos":22018000},"fetch_binary":{"secs":3,"nanos":988224000},"verify_binary":{"secs":2,"nanos":917281000},"verify_binary_naive":{"secs":0,"nanos":959305000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:45:18.033419.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:45:18.033419.json new file mode 100644 index 0000000..a398e0d --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:45:18.033419.json @@ -0,0 +1 @@ +{"start_time":4424461,"end_time":20883436,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":91739000},"update_repo":{"secs":3,"nanos":810141000},"fetch_signature":{"secs":0,"nanos":24881000},"fetch_binary":{"secs":3,"nanos":970374000},"verify_binary":{"secs":2,"nanos":917278000},"verify_binary_naive":{"secs":0,"nanos":959295000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:46:16.094788.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:46:16.094788.json new file mode 100644 index 0000000..3c47c21 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:46:16.094788.json @@ -0,0 +1 @@ +{"start_time":4475068,"end_time":20821419,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":119647000},"update_repo":{"secs":3,"nanos":776583000},"fetch_signature":{"secs":0,"nanos":24454000},"fetch_binary":{"secs":3,"nanos":891913000},"verify_binary":{"secs":2,"nanos":917278000},"verify_binary_naive":{"secs":0,"nanos":959254000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:47:14.155796.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:47:14.155796.json new file mode 100644 index 0000000..d425ce9 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:47:14.155796.json @@ -0,0 +1 @@ +{"start_time":4451024,"end_time":20859574,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":83227000},"update_repo":{"secs":3,"nanos":808541000},"fetch_signature":{"secs":0,"nanos":23603000},"fetch_binary":{"secs":3,"nanos":955470000},"verify_binary":{"secs":2,"nanos":917308000},"verify_binary_naive":{"secs":0,"nanos":959277000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:48:12.217400.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:48:12.217400.json new file mode 100644 index 0000000..6c58492 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:48:12.217400.json @@ -0,0 +1 @@ +{"start_time":4451544,"end_time":20857826,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":88728000},"update_repo":{"secs":3,"nanos":811129000},"fetch_signature":{"secs":0,"nanos":26185000},"fetch_binary":{"secs":3,"nanos":933758000},"verify_binary":{"secs":2,"nanos":917359000},"verify_binary_naive":{"secs":0,"nanos":959273000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:49:10.278672.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:49:10.278672.json new file mode 100644 index 0000000..f957523 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:49:10.278672.json @@ -0,0 +1 @@ +{"start_time":4486655,"end_time":20859733,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":73763000},"update_repo":{"secs":3,"nanos":818224000},"fetch_signature":{"secs":0,"nanos":22330000},"fetch_binary":{"secs":3,"nanos":906519000},"verify_binary":{"secs":2,"nanos":917177000},"verify_binary_naive":{"secs":0,"nanos":959329000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:50:08.440536.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:50:08.440536.json new file mode 100644 index 0000000..ce12fbc --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:50:08.440536.json @@ -0,0 +1 @@ +{"start_time":4429037,"end_time":20864129,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":83634000},"update_repo":{"secs":3,"nanos":813296000},"fetch_signature":{"secs":0,"nanos":24058000},"fetch_binary":{"secs":3,"nanos":948885000},"verify_binary":{"secs":2,"nanos":917345000},"verify_binary_naive":{"secs":0,"nanos":959325000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:51:06.503707.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:51:06.503707.json new file mode 100644 index 0000000..af9deb3 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:51:06.503707.json @@ -0,0 +1 @@ +{"start_time":4455417,"end_time":20783324,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":50558000},"update_repo":{"secs":3,"nanos":810884000},"fetch_signature":{"secs":0,"nanos":23444000},"fetch_binary":{"secs":3,"nanos":890586000},"verify_binary":{"secs":2,"nanos":917240000},"verify_binary_naive":{"secs":0,"nanos":959335000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:52:04.667937.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:52:04.667937.json new file mode 100644 index 0000000..bbe7dab --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:52:04.667937.json @@ -0,0 +1 @@ +{"start_time":4439676,"end_time":20904479,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":104842000},"update_repo":{"secs":3,"nanos":791111000},"fetch_signature":{"secs":0,"nanos":32545000},"fetch_binary":{"secs":3,"nanos":971766000},"verify_binary":{"secs":2,"nanos":917343000},"verify_binary_naive":{"secs":0,"nanos":959246000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:02.554193.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:02.554193.json new file mode 100644 index 0000000..1ac19d1 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:02.554193.json @@ -0,0 +1 @@ +{"start_time":4450672,"end_time":20714495,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84104000},"update_repo":{"secs":3,"nanos":812654000},"fetch_signature":{"secs":0,"nanos":27560000},"fetch_binary":{"secs":3,"nanos":777314000},"verify_binary":{"secs":2,"nanos":917326000},"verify_binary_naive":{"secs":0,"nanos":959237000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:59.971332.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:59.971332.json new file mode 100644 index 0000000..8e51fef --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:53:59.971332.json @@ -0,0 +1 @@ +{"start_time":4446863,"end_time":20186328,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84252000},"update_repo":{"secs":3,"nanos":811797000},"fetch_signature":{"secs":0,"nanos":23274000},"fetch_binary":{"secs":3,"nanos":279902000},"verify_binary":{"secs":2,"nanos":917262000},"verify_binary_naive":{"secs":0,"nanos":959300000}} \ No newline at end of file diff --git a/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:54:58.135862.json b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:54:58.135862.json new file mode 100644 index 0000000..7e11828 --- /dev/null +++ b/benchmark-output/benchmark-levels/performance-benchmark-2024-08-23 11:54:58.135862.json @@ -0,0 +1 @@ +{"start_time":4478266,"end_time":20936361,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":53450000},"update_repo":{"secs":3,"nanos":812247000},"fetch_signature":{"secs":0,"nanos":22467000},"fetch_binary":{"secs":4,"nanos":12975000},"verify_binary":{"secs":2,"nanos":917343000},"verify_binary_naive":{"secs":0,"nanos":959267000}} \ No newline at end of file diff --git a/benchmark-output/benchmarks-size.json b/benchmark-output/benchmarks-size.json new file mode 100644 index 0000000..33f24ef --- /dev/null +++ b/benchmark-output/benchmarks-size.json @@ -0,0 +1 @@ +{"benchmark-code-size-full": {"raw_size": 266928, "cargo_bloat": {"file-size": 2393100, "text-section-size": 238104, "functions": [{"crate": "benchmark_code_size_full", "name": "benchmark_code_size_full::____embassy_main_task::{{closure}}", "size": 37328}, {"crate": "bt4ot_embedded", "name": "der::reader::Reader::read_nested", "size": 13240}, {"crate": "tuf_no_std", "name": "der::decode::Decode::from_der", "size": 9836}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ethernet::::process_ethernet", "size": 9676}, {"crate": "tuf_no_std", "name": "::decode_role", "size": 9136}, {"crate": "tuf_no_std", "name": "::decode_role", "size": 7860}, {"crate": "tuf_no_std", "name": " as tuf_no_std::role::root::TufRoot>::role_keys", "size": 5868}, {"crate": "p256", "name": "::mul", "size": 5526}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 5452}, {"crate": "tuf_no_std", "name": "::decode_role", "size": 4424}, {"crate": "cyw43", "name": "cyw43::runner::Runner::check_status::{{closure}}", "size": 4320}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::dispatch_ip", "size": 4264}, {"crate": "bt4ot_embedded", "name": "der::reader::Reader::decode", "size": 3884}, {"crate": "sha2", "name": "sha2::sha256::compress256", "size": 3440}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 2528}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_mul", "size": 2422}, {"crate": "pem_rfc7468", "name": "pem_rfc7468::decoder::decode", "size": 1996}, {"crate": "bt4ot_embedded", "name": "bt4ot_embedded::verify_signature", "size": 1792}, {"crate": "tuf_no_std", "name": " as tuf_no_std::role::root::TufRoot>::verify_signature", "size": 1712}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::montgomery_reduce", "size": 1670}, {"crate": "pem_rfc7468", "name": "::decode", "size": 1596}, {"crate": "pem_rfc7468", "name": "base64ct::decoder::Decoder::perform_decode", "size": 1556}, {"crate": "std", "name": "core::fmt::Formatter::pad", "size": 1536}, {"crate": "bt4ot_embedded", "name": "der::reader::Reader::decode", "size": 1488}, {"crate": "spki", "name": "spki::algorithm::AlgorithmIdentifier::assert_oids", "size": 1294}, {"crate": "tuf_no_std", "name": "tuf_no_std::fetch_and_verify_target_file_async::{{closure}}", "size": 1244}, {"crate": "pkcs8", "name": "der::reader::Reader::decode", "size": 1184}, {"crate": "bt4ot_ota_common", "name": "bt4ot_ota_common::fetch_file::{{closure}}", "size": 1148}, {"crate": "tuf_no_std", "name": "tuf_no_std::role::root::::encode_canonically", "size": 1090}, {"crate": "std", "name": "core::char::methods::::escape_debug_ext", "size": 1020}, {"crate": "tuf_no_std_der", "name": " as core::convert::TryFrom>>::try_from", "size": 1016}, {"crate": "tuf_no_std", "name": "der::reader::Reader::decode", "size": 960}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u64_div_rem", "size": 938}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::write", "size": 828}, {"crate": "der", "name": "der::datetime::DateTime::from_unix_duration", "size": 820}, {"crate": "std", "name": "compiler_builtins::mem::memmove", "size": 788}, {"crate": "bt4ot_embedded", "name": "der::asn1::optional::>::der_cmp", "size": 778}, {"crate": "bt4ot_ota_common", "name": "bt4ot_ota_common::net::Transport::fetch_role::{{closure}}", "size": 776}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::parse_ack", "size": 728}, {"crate": "std", "name": "core::str::slice_error_fail_rt", "size": 668}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::poll_at::{{closure}}", "size": 650}, {"crate": "der", "name": "der::datetime::DateTime::new", "size": 648}, {"crate": "bt4ot_embedded", "name": "bt4ot_embedded::parse_cert", "size": 632}, {"crate": "der?", "name": "::fmt", "size": 612}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 608}, {"crate": "std", "name": "::write_str", "size": 604}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::write::{{closure}}", "size": 584}, {"crate": "tuf_no_std_common", "name": ">::add", "size": 560}, {"crate": "std", "name": "core::str::converts::from_utf8", "size": 552}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::connect::{{closure}}", "size": 552}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Repr::emit", "size": 544}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::read::{{closure}}", "size": 532}, {"crate": "tuf_no_std", "name": "tuf_no_std_common::crypto::verify_sha256_impl", "size": 528}, {"crate": "tuf_no_std", "name": "der::reader::Reader::decode", "size": 524}, {"crate": "tuf_no_std_common", "name": " as core::ops::arith::Mul>::mul", "size": 512}, {"crate": "bt4ot_embedded", "name": "primeorder::projective::ProjectivePoint::mul", "size": 512}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Repr::emit", "size": 500}, {"crate": "p256", "name": "p256::arithmetic::scalar::scalar_impl::subtract_n_if_necessary", "size": 488}, {"crate": "der", "name": "::decode", "size": 484}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral", "size": 478}, {"crate": "bt4ot_embedded", "name": "der::asn1::context_specific::ContextSpecific::decode_implicit", "size": 476}, {"crate": "der", "name": "<&T as core::fmt::Display>::fmt", "size": 472}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::sub_inner", "size": 464}, {"crate": "x509_cert", "name": "::decode", "size": 456}, {"crate": "x509_cert", "name": "::decode", "size": 456}, {"crate": "x509_cert", "name": "::decode", "size": 456}, {"crate": "tuf_no_std", "name": "::decode", "size": 456}, {"crate": "tuf_no_std", "name": "::decode", "size": 456}, {"crate": "tuf_no_std", "name": "::decode", "size": 456}, {"crate": "tuf_no_std", "name": "::decode", "size": 456}, {"crate": "tuf_no_std", "name": "::decode", "size": 456}, {"crate": "tuf_no_std", "name": "::decode", "size": 456}, {"crate": "pkcs8", "name": "::decode", "size": 456}, {"crate": "pkcs8", "name": "::decode", "size": 456}, {"crate": "bt4ot_embedded", "name": "::encode", "size": 456}, {"crate": "bt4ot_embedded", "name": ">::double", "size": 448}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 440}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 440}, {"crate": "serde_json_core?", "name": "<&mut serde_json_core::de::Deserializer as serde::de::Deserializer>::deserialize_ignored_any", "size": 438}, {"crate": "bt4ot_embedded", "name": "::encode", "size": 436}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_reset::{{closure}}", "size": 428}, {"crate": "embassy_net", "name": "embassy_net::Inner::apply_static_config", "size": 428}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::TcpOption::emit", "size": 412}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}", "size": 408}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_disable::{{closure}}", "size": 400}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::run", "size": 400}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar::{{closure}}", "size": 396}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u32_div_rem", "size": 392}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write::{{closure}}", "size": 384}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read::{{closure}}", "size": 376}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::new", "size": 360}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}", "size": 352}, {"crate": "cyw43", "name": "cyw43::runner::Runner::handle_irq::{{closure}}", "size": 344}, {"crate": "p256", "name": "::invert", "size": 340}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_add", "size": 340}, {"crate": "std", "name": "compiler_builtins::mem::memcpy", "size": 334}, {"crate": "const_oid", "name": "const_oid::arcs::Arcs::try_next", "size": 332}, {"crate": "std", "name": "core::fmt::write", "size": 330}, {"crate": "der", "name": ">::try_from", "size": 324}, {"crate": "std", "name": "core::fmt::builders::DebugStruct::field", "size": 324}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::encode_value", "size": 320}, {"crate": "tuf_no_std", "name": "der::reader::Reader::decode", "size": 318}, {"crate": "ecdsa", "name": "::decode", "size": 312}, {"crate": "tuf_no_std", "name": "::encode", "size": 308}, {"crate": "x509_cert", "name": "der::reader::Reader::decode", "size": 304}, {"crate": "p256", "name": "::invert_vartime", "size": 304}, {"crate": "x509_cert", "name": " as der::reader::Reader>::read_into", "size": 298}, {"crate": "cyw43", "name": "cyw43::control::Control::get_iovar::{{closure}}", "size": 292}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_set_window::{{closure}}", "size": 288}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_reply", "size": 280}, {"crate": "tuf_no_std", "name": "::decode", "size": 276}, {"crate": "pkcs8", "name": "<[T] as core::fmt::Debug>::fmt", "size": 276}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl::{{closure}}", "size": 272}, {"crate": "tuf_no_std", "name": "der::reader::Reader::decode", "size": 264}, {"crate": "tuf_no_std_der", "name": "::value_len", "size": 260}, {"crate": "tuf_no_std", "name": "heapless::indexmap::IndexMap::swap_remove", "size": 260}, {"crate": "std", "name": "core::unicode::printable::check", "size": 260}, {"crate": "tuf_no_std_der", "name": " as elliptic_curve::point::DecompressPoint>::decompress", "size": 256}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 256}, {"crate": "bt4ot_embedded", "name": "der::asn1::optional::>::encode", "size": 254}, {"crate": "std", "name": "core::fmt::builders::DebugTuple::field", "size": 252}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 248}, {"crate": "pio", "name": "pio::InstructionOperands::encode", "size": 244}, {"crate": "p256", "name": "::sqrt", "size": 244}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 244}, {"crate": "tuf_no_std_der", "name": "::value_len", "size": 238}, {"crate": "tuf_no_std", "name": "der::reader::Reader::decode", "size": 238}, {"crate": "sha2", "name": "sha2::sha256::soft::schedule", "size": 238}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 236}, {"crate": "tuf_no_std", "name": "::encode", "size": 232}, {"crate": "der", "name": "::der_cmp", "size": 232}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wlan_write::{{closure}}", "size": 232}, {"crate": "sha2", "name": "sha2::sha256::soft::sha256_digest_round_x2", "size": 230}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wait_for_event::{{closure}}", "size": 224}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reset", "size": 222}, {"crate": "tuf_no_std_common", "name": "::encode", "size": 220}, {"crate": "spki", "name": "::write", "size": 220}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_broadcast_v4", "size": 220}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup", "size": 220}, {"crate": "tuf_no_std_der", "name": "::value_len", "size": 218}, {"crate": "tuf_no_std", "name": "::decode", "size": 218}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::erase", "size": 216}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::remove_default_ipv4_route", "size": 204}, {"crate": "der", "name": "der::asn1::utc_time::UtcTime::from_unix_duration", "size": 204}, {"crate": "const_oid?", "name": "::fmt", "size": 204}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32x2::{{closure}}", "size": 204}, {"crate": "smoltcp", "name": "heapless::linear_map::LinearMap::insert", "size": 196}, {"crate": "cyw43", "name": "cyw43::bus::Bus::readn::{{closure}}", "size": 196}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::flush::{{closure}}", "size": 196}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::rst_reply", "size": 190}, {"crate": "p256", "name": "::add_assign", "size": 188}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ipv4::::icmpv4_reply", "size": 186}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Repr::parse", "size": 184}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_str", "size": 184}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_writen::{{closure}}", "size": 184}, {"crate": "base64ct", "name": "::next", "size": 182}, {"crate": "tuf_no_std", "name": "<&T as core::fmt::Debug>::fmt", "size": 180}, {"crate": "tuf_no_std", "name": " as der::encode::EncodeValue>::value_len", "size": 180}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::seq_to_transmit", "size": 180}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 180}, {"crate": "tuf_no_std_der", "name": " as der::encode::EncodeValue>::value_len", "size": 176}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 176}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 176}, {"crate": "embassy_rp", "name": "embassy_rp::pio::Common::try_load_program_at", "size": 176}, {"crate": "tuf_no_std_der", "name": " as der::encode::EncodeValue>::value_len", "size": 174}, {"crate": "std", "name": "core::cmp::PartialEq::ne", "size": 172}, {"crate": "bt4ot_embedded", "name": "::value_len", "size": 172}, {"crate": "tuf_no_std_der", "name": "::value_len", "size": 168}, {"crate": "std", "name": "core::cmp::impls:: for &A>::lt", "size": 168}, {"crate": "tuf_no_std", "name": "<(U,T) as core::fmt::Debug>::fmt", "size": 168}, {"crate": "const_oid", "name": "<&T as core::fmt::Display>::fmt", "size": 168}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_readn::{{closure}}", "size": 168}, {"crate": "der", "name": "::read_slice", "size": 166}, {"crate": "std", "name": "compiler_builtins::mem::memset", "size": 160}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::add::put", "size": 158}, {"crate": "tuf_no_std_der", "name": " as der::encode::EncodeValue>::value_len", "size": 156}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_many", "size": 156}, {"crate": "tuf_no_std_der", "name": " as der::encode::EncodeValue>::value_len", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::data", "size": 152}, {"crate": "panic_probe", "name": "<&T as core::fmt::Display>::fmt", "size": 152}, {"crate": "der", "name": "der::tag::Tag::octet", "size": 152}, {"crate": "std", "name": "core::panicking::assert_failed_inner", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 152}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 152}, {"crate": "tuf_no_std_der", "name": " as der::encode::EncodeValue>::value_len", "size": 150}, {"crate": "tuf_no_std_der", "name": " as der::encode::EncodeValue>::value_len", "size": 148}, {"crate": "smoltcp", "name": "smoltcp::iface::neighbor::Cache::lookup", "size": 148}, {"crate": "pkcs8", "name": "::encode", "size": 148}, {"crate": "embassy_net", "name": "::drop", "size": 148}, {"crate": "der", "name": "::encode", "size": 148}, {"crate": "cyw43", "name": "cyw43::bus::Bus::writen::{{closure}}", "size": 148}, {"crate": "tuf_no_std_der", "name": "::value_len", "size": 146}, {"crate": "tuf_no_std", "name": "heapless::indexmap::IndexMap::find", "size": 146}, {"crate": "tuf_no_std", "name": "der::reader::Reader::decode", "size": 146}, {"crate": "tuf_no_std_der", "name": "::value_len", "size": 144}, {"crate": "tuf_no_std_common", "name": "<&T as core::fmt::Debug>::fmt", "size": 144}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::next_header", "size": 144}, {"crate": "std", "name": "core::fmt::Formatter::debug_tuple_field1_finish", "size": 144}, {"crate": "const_oid", "name": "const_oid::ObjectIdentifier::from_bytes", "size": 144}, {"crate": "bt4ot_embedded", "name": "::value_len", "size": 144}, {"crate": "bt4ot_embedded", "name": "<&T as core::fmt::Debug>::fmt", "size": 144}, {"crate": "spki?", "name": "::fmt", "size": 140}, {"crate": "defmt_rtt", "name": "_defmt_write", "size": 140}, {"crate": "sec1?", "name": "::fmt", "size": 136}, {"crate": "p256", "name": ">::sub_assign", "size": 136}, {"crate": "der", "name": "::fmt", "size": 136}, {"crate": "sec1?", "name": "::fmt", "size": 136}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write32_swapped::{{closure}}", "size": 136}, {"crate": "tuf_no_std", "name": "::encode", "size": 134}, {"crate": "pkcs8", "name": "::decode_value", "size": 132}, {"crate": "pkcs8", "name": "::decode_value", "size": 132}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm", "size": 132}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32_swapped::{{closure}}", "size": 132}, {"crate": "embedded_io_async", "name": "embedded_io_async::Read::read_exact::{{closure}}", "size": 132}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_function_pointers_with_boot2", "size": 128}, {"crate": "std", "name": "core::fmt::Formatter::debug_struct_field2_finish", "size": 128}, {"crate": "defmt", "name": "core::fmt::Write::write_char", "size": 126}, {"crate": "tuf_no_std_common", "name": ">::eq", "size": 124}, {"crate": "panic_probe", "name": "rust_begin_unwind", "size": 124}, {"crate": "cyw43", "name": "cyw43::control::Control::gpio_set::{{closure}}", "size": 124}, {"crate": "std", "name": "__divmoddi4", "size": 122}, {"crate": "tuf_no_std", "name": "::decode", "size": 122}, {"crate": "pkcs8", "name": "::decode", "size": 122}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::Repr::buffer_len", "size": 120}, {"crate": "embassy_net", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 120}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 120}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 120}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "x509_cert", "name": "::value_len", "size": 118}, {"crate": "heapless?", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 118}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::fill_checksum", "size": 116}, {"crate": "embassy_rp", "name": "IO_IRQ_BANK0", "size": 116}, {"crate": "std", "name": "core::fmt::Formatter::debug_struct_field1_finish", "size": 116}, {"crate": "tuf_no_std", "name": "::encode", "size": 114}, {"crate": "tuf_no_std_common", "name": "<&T as core::fmt::Debug>::fmt", "size": 112}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_sub", "size": 112}, {"crate": "der", "name": "::write", "size": 112}, {"crate": "der", "name": "der::asn1::integer::uint::decode_to_array", "size": 112}, {"crate": "der", "name": "der::asn1::integer::uint::decode_to_array", "size": 112}, {"crate": "bt4ot_embedded", "name": "<&T as core::fmt::Debug>::fmt", "size": 112}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy4", "size": 110}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::advance_position", "size": 110}, {"crate": "pkcs8", "name": "::encode", "size": 110}, {"crate": "bt4ot_embedded", "name": "der::datetime::encode_decimal", "size": 110}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::advance_position", "size": 108}, {"crate": "pkcs8", "name": "::encode", "size": 108}, {"crate": "embassy_executor", "name": "_embassy_time_schedule_wake", "size": 108}, {"crate": "der", "name": "der::asn1::integer::uint::decode_to_array", "size": 108}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl_set_u32::{{closure}}", "size": 108}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::advance_position", "size": 106}, {"crate": "tuf_no_std", "name": "::encode", "size": 106}, {"crate": "tuf_no_std", "name": "der::datetime::encode_decimal", "size": 104}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::advance_position", "size": 104}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::remove", "size": 104}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_unallocated", "size": 104}, {"crate": "pkcs8", "name": "::encode", "size": 104}, {"crate": "embassy_rp", "name": "embassy_rp::time_driver::TimerDriver::check_alarm", "size": 104}, {"crate": "der", "name": "der::writer::slice::SliceWriter::encode", "size": 104}, {"crate": "embassy_boot?", "name": "::fmt", "size": 104}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::advance_position", "size": 102}, {"crate": "std", "name": "::fmt", "size": 102}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::advance_position", "size": 100}, {"crate": "embassy_rp", "name": "DMA_IRQ_0", "size": 100}, {"crate": "der", "name": "der::reader::Reader::read_into", "size": 100}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 100}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32::{{closure}}", "size": 100}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::advance_position", "size": 98}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset4", "size": 96}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::new", "size": 96}, {"crate": "smoltcp", "name": "::fmt", "size": 96}, {"crate": "pkcs8", "name": " as der::encode::EncodeValue>::value_len", "size": 96}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::advance_position", "size": 96}, {"crate": "pem_rfc7468", "name": "base64ct::alphabet::Alphabet::decode_6bits", "size": 96}, {"crate": "cyw43", "name": "cyw43::structs::BdcHeader::parse", "size": 96}, {"crate": "std", "name": "core::ptr::drop_in_place>>", "size": 96}, {"crate": "embassy_embedded_hal", "name": "embassy_embedded_hal::flash::partition::blocking::BlockingPartition::new", "size": 96}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::new", "size": 94}, {"crate": "embassy_boot", "name": "embassy_boot::firmware_updater::blocking::BlockingFirmwareState::get_state", "size": 94}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::new", "size": 92}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reply", "size": 92}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write16::{{closure}}", "size": 92}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::ReadNorFlash>::read", "size": 92}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::new", "size": 90}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::route", "size": 90}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_neighbor", "size": 90}, {"crate": "der", "name": "der::asn1::bit_string::BitStringRef::new", "size": 90}, {"crate": "generic_array", "name": "generic_array::GenericArray::clone_from_slice", "size": 88}, {"crate": "tuf_no_std", "name": "der::reader::nested::NestedReader::new", "size": 88}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 88}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup::{{closure}}", "size": 88}, {"crate": "p256", "name": ">>::reduce_bytes", "size": 88}, {"crate": "der", "name": "der::tag::Tag::assert_eq", "size": 88}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 88}, {"crate": "serde", "name": "serde::de::MapAccess::next_value", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write32::{{closure}}", "size": 88}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::new", "size": 86}, {"crate": "der", "name": "der::asn1::integer::uint::encode_bytes", "size": 86}, {"crate": "tuf_no_std", "name": " as core::iter::traits::collect::FromIterator>::from_iter", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::pseudo_header", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::challenge_ack_reply", "size": 84}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::new", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::register", "size": 84}, {"crate": "embassy_net", "name": "defmt::export::fmt", "size": 84}, {"crate": "defmt_rtt", "name": "_defmt_acquire", "size": 84}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::write_impl", "size": 84}, {"crate": "std", "name": "core::ops::function::FnMut::call_mut", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write8::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read8::{{closure}}", "size": 84}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Receiver::receive_done", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::send_done", "size": 84}, {"crate": "tuf_no_std_common", "name": " as der::encode::EncodeValue>::value_len", "size": 80}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::new_checked", "size": 80}, {"crate": "smoltcp", "name": "::next", "size": 80}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::to_canonical", "size": 80}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_range_program", "size": 80}, {"crate": "std?", "name": " as serde::de::DeserializeSeed>::deserialize", "size": 80}, {"crate": "std?", "name": " as serde::de::DeserializeSeed>::deserialize", "size": 80}, {"crate": "std?", "name": " as serde::de::DeserializeSeed>::deserialize", "size": 80}, {"crate": "der", "name": "der::asn1::integer::uint::decode_to_slice", "size": 78}, {"crate": "x509_cert", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "x509_cert", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "spki?", "name": " as core::fmt::Debug>::fmt", "size": 76}, {"crate": "tuf_no_std", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::DhcpOptionWriter::emit", "size": 76}, {"crate": "smoltcp", "name": "::fmt", "size": 76}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::header_len", "size": 76}, {"crate": "pkcs8", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "pkcs8", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::array::>::from_be_byte_array", "size": 76}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 76}, {"crate": "der?", "name": "::fmt", "size": 76}, {"name": "__aeabi_lmul", "size": 74}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::to_bytes", "size": 74}, {"crate": "heapless", "name": "heapless::vec::Vec::from_slice", "size": 72}, {"crate": "smoltcp", "name": "heapless::vec::Vec::clone", "size": 72}, {"crate": "embassy_rp", "name": "::acquire", "size": 72}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::set_timeout", "size": 72}, {"crate": "der", "name": "der::length::Length::initial_octet", "size": 72}, {"crate": "der", "name": "der::reader::slice::SliceReader::remaining", "size": 72}, {"crate": "defmt_rtt", "name": "_defmt_release", "size": 72}, {"crate": "hex", "name": "hex::val", "size": 72}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::poll_send", "size": 72}, {"crate": "std", "name": "__clzsi2", "size": 70}, {"crate": "der", "name": "der::length::Length::for_tlv", "size": 70}, {"crate": "tuf_no_std", "name": " as tuf_no_std::role::root::TufRoot>::role_threshold", "size": 68}, {"crate": "std", "name": ">::into", "size": 68}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::in_same_network", "size": 68}, {"crate": "p256", "name": "crypto_bigint::uint::add::>::wrapping_add", "size": 68}, {"crate": "std", "name": "core::result::unwrap_failed", "size": 68}, {"crate": "std", "name": "::write_char", "size": 68}, {"crate": "bt4ot_embedded", "name": "sec1::point::EncodedPoint::tag", "size": 68}, {"crate": "x509_cert", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "x509_cert", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "x509_cert", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "tuf_no_std", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "tuf_no_std", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "tuf_no_std", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "tuf_no_std", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "tuf_no_std", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "tuf_no_std", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "pkcs8", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "pkcs8", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "der", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "tuf_no_std", "name": "tuf_no_std::signature::::keyid", "size": 64}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 64}, {"crate": "pkcs8", "name": "::encode", "size": 64}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::from_bytes", "size": 64}, {"crate": "p256", "name": ">::shr_assign", "size": 64}, {"crate": "p256", "name": " as subtle::ConditionallySelectable>::conditional_select", "size": 64}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 64}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 64}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::blocking_write", "size": 64}, {"crate": "const_oid", "name": "::next", "size": 64}, {"crate": "bt4ot_embedded", "name": "::decode_value", "size": 64}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 64}, {"crate": "x509_cert", "name": " as der::encode::EncodeValue>::value_len", "size": 62}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_ident", "size": 62}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::sqn", "size": 62}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral::write_prefix", "size": 62}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 62}, {"crate": "std", "name": " as core::fmt::Debug>::fmt", "size": 60}, {"crate": "tuf_no_std", "name": "::decode_role", "size": 60}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_one", "size": 60}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 60}, {"crate": "pkcs8", "name": "<&T as core::fmt::Debug>::fmt", "size": 60}, {"crate": "pkcs8", "name": "::encode_value", "size": 60}, {"crate": "std", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 60}, {"crate": "der?", "name": "::eq", "size": 60}, {"crate": "std", "name": "core::slice::::copy_from_slice::len_mismatch_fail", "size": 60}, {"crate": "std", "name": " as core::fmt::Debug>::fmt", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_index_order_fail", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_end_index_len_fail", "size": 60}, {"crate": "std", "name": "core::panicking::panic_bounds_check", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_start_index_len_fail", "size": 60}, {"crate": "const_oid", "name": "::fmt", "size": 60}, {"crate": "std", "name": "core::ptr::drop_in_place>::init::{{closure}}>", "size": 60}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::wake", "size": 58}, {"crate": "bt4ot_embedded", "name": "der::asn1::optional::>::encoded_len", "size": 58}, {"crate": "serde_json_core?", "name": "<&mut serde_json_core::de::Deserializer as serde::de::Deserializer>::deserialize_str", "size": 58}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 58}, {"crate": "tuf_no_std_common", "name": "der::encode::EncodeValue::header", "size": 56}, {"crate": "tuf_no_std_common", "name": "elliptic_curve::scalar::primitive::ScalarPrimitive::from_slice", "size": 56}, {"crate": "tuf_no_std", "name": "heapless::vec::Vec::extend_from_slice", "size": 56}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_ip_addr", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "pkcs8", "name": "der::encode::EncodeValue::header", "size": 56}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "embassy_executor", "name": "__pender", "size": 56}, {"crate": "std", "name": "core::panicking::panic_explicit", "size": 56}, {"crate": "bt4ot_embedded", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "bt4ot_embedded", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "bt4ot_embedded", "name": "der::encode::EncodeValue::header", "size": 56}, {"name": "PIO0_IRQ_0", "size": 56}, {"crate": "heapless", "name": "heapless::deque::Deque::pop_front", "size": 56}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 56}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 56}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::try_send", "size": 56}, {"crate": "tuf_no_std", "name": "der::reader::Reader::decode", "size": 54}, {"crate": "pkcs8", "name": "der::encode::EncodeValue::header", "size": 54}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::register", "size": 54}, {"crate": "der", "name": "der::error::Error::nested", "size": 54}, {"crate": "subtle", "name": "subtle::CtOption::unwrap", "size": 52}, {"crate": "tuf_no_std_common", "name": "sec1::point::EncodedPoint::tag", "size": 52}, {"crate": "tuf_no_std", "name": "der::arrayvec::ArrayVec::new", "size": 52}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet<&T>::payload", "size": 52}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_whitespace", "size": 52}, {"crate": "embassy_time", "name": "::poll", "size": 52}, {"crate": "embassy_rp", "name": "__pre_init", "size": 52}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm_callback", "size": 52}, {"crate": "embassy_net", "name": "embassy_net::to_smoltcp_hardware_address", "size": 52}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::new", "size": 52}, {"crate": "der", "name": "::fmt", "size": 52}, {"crate": "std", "name": "core::slice::::split_at_mut", "size": 52}, {"crate": "std", "name": "core::option::expect_failed", "size": 52}, {"crate": "std", "name": "core::slice::::split_at", "size": 52}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_reset::{{closure}}>", "size": 52}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::register", "size": 50}, {"crate": "pem_rfc7468", "name": "pem_rfc7468::grammar::strip_trailing_eol", "size": 50}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Address::from_bytes", "size": 48}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 48}, {"crate": "smoltcp", "name": "defmt::traits::Format::_format_data", "size": 48}, {"crate": "smoltcp", "name": "::sub", "size": 48}, {"crate": "smoltcp", "name": ">::add", "size": 48}, {"crate": "serde_json_core", "name": "::unit_variant", "size": 48}, {"crate": "pem_rfc7468", "name": "base64ct::decoder::Decoder::advance_line", "size": 48}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::Uint<_>::from_words", "size": 48}, {"crate": "embassy_hal_internal", "name": "embassy_hal_internal::interrupt::InterruptExt::set_priority", "size": 48}, {"crate": "der", "name": "der::asn1::integer::uint::encoded_len", "size": 48}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_mutably_borrowed", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_borrowed", "size": 48}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 48}, {"crate": "bt4ot_embedded", "name": "ecdsa::der::Signature::s", "size": 48}, {"crate": "bt4ot_embedded", "name": "ecdsa::der::Signature::r", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_src_addr", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_dst_addr", "size": 48}, {"crate": "tuf_no_std_common?", "name": "::fmt", "size": 48}, {"crate": "embassy_sync?", "name": "::fmt", "size": 48}, {"crate": "std", "name": "compiler_builtins::mem::memcmp", "size": 46}, {"crate": "x509_cert", "name": " as der::reader::Reader>::peek_byte", "size": 46}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::peek_byte", "size": 46}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::end_map", "size": 46}, {"crate": "base64ct", "name": "base64ct::decoder::Line::trim_end", "size": 46}, {"name": "__aeabi_llsl", "size": 44}, {"crate": "tuf_no_std_der", "name": "::encoded_len", "size": 44}, {"crate": "tuf_no_std_der", "name": "::encoded_len", "size": 44}, {"crate": "tuf_no_std_der", "name": "::encoded_len", "size": 44}, {"crate": "tuf_no_std_der", "name": "::encoded_len", "size": 44}, {"crate": "std", "name": "core::result::Result::expect", "size": 44}, {"crate": "tuf_no_std", "name": "heapless::indexmap::hash_with", "size": 44}, {"crate": "byteorder", "name": "::write_u32", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::msg_type", "size": 44}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 44}, {"crate": "defmt", "name": "defmt::impls::primitives::::_format_data", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_to_transmit", "size": 44}, {"crate": "pkcs8", "name": "::encoded_len", "size": 44}, {"crate": "pkcs8", "name": "::encoded_len", "size": 44}, {"crate": "std", "name": "core::result::Result::and_then", "size": 44}, {"crate": "embassy_rp", "name": "_embassy_time_allocate_alarm", "size": 44}, {"crate": "bt4ot_embedded", "name": "::encoded_len", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_ethertype", "size": 44}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::new_checked", "size": 42}, {"crate": "der", "name": "der::length:: for core::result::Result>::add", "size": 42}, {"crate": "der", "name": "core::slice::cmp::::cmp", "size": 42}, {"crate": "heapless?", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_disable::{{closure}}>", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::handle_irq::{{closure}}>", "size": 42}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&mut T>::data_mut", "size": 40}, {"crate": "pem_rfc7468", "name": "pem_rfc7468::decoder::check_for_headers", "size": 40}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::cmp::>::ct_lt", "size": 40}, {"crate": "p256", "name": "elliptic_curve::scalar::primitive::ScalarPrimitive::new", "size": 40}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 40}, {"crate": "der", "name": "::peek_byte", "size": 40}, {"crate": "der", "name": "der::asn1::integer::uint::strip_leading_zeroes", "size": 40}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_rem_by_zero", "size": 40}, {"crate": "const_oid", "name": ">::try_from", "size": 40}, {"crate": "std", "name": ">::into", "size": 40}, {"crate": "byteorder", "name": "::write_u16", "size": 40}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 40}, {"crate": "cortex_m_rt", "name": "Reset", "size": 40}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Timer::should_keep_alive", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::contiguous_window", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_unicast_v4", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::window_to_update", "size": 38}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_object_colon", "size": 38}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe32_to_fe64", "size": 38}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::cmp::>::ct_eq", "size": 38}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::wake", "size": 38}, {"crate": "const_oid", "name": "<&T as core::fmt::Debug>::fmt", "size": 38}, {"crate": "embassy_net?", "name": "::clone", "size": 38}, {"name": "__aeabi_idiv", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_ident", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_type", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::payload_mut", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_ident", "size": 36}, {"crate": "byteorder", "name": "::read_u16", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::reset", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::total_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::scaled_window", "size": 36}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::end", "size": 36}, {"crate": "pkcs8", "name": " as der::reader::Reader>::peek_byte", "size": 36}, {"crate": "pem_rfc7468", "name": "core::slice::::starts_with", "size": 36}, {"crate": "fixed", "name": "<&T as core::fmt::Debug>::fmt", "size": 36}, {"crate": "embassy_time", "name": ">::add", "size": 36}, {"crate": "embassy_rp", "name": "::release", "size": 36}, {"crate": "embassy_net_driver", "name": "defmt::export::fmt_array", "size": 36}, {"crate": "der", "name": "::add", "size": 36}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 36}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 36}, {"crate": "std", "name": "::fmt", "size": 36}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_async_fn_resumed", "size": 36}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_div_by_zero", "size": 36}, {"crate": "std", "name": "core::panicking::panic", "size": 36}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 36}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 36}, {"crate": "embassy_net_driver_channel?", "name": " as embassy_net_driver::Driver>::hardware_address", "size": 36}, {"crate": "cortex_m", "name": "cortex_m::peripheral::scb::::sys_reset", "size": 36}, {"crate": "embassy_rp?", "name": " as core::future::future::Future>::poll", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 36}, {"crate": "tuf_no_std_common?", "name": "::fmt", "size": 36}, {"crate": "der?", "name": "::fmt", "size": 36}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 36}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::set_pindir", "size": 36}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 36}, {"crate": "tuf_no_std_common", "name": "elliptic_curve::scalar::primitive::ScalarPrimitive::is_zero", "size": 34}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 34}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::set_state", "size": 34}, {"crate": "ecdsa", "name": "ecdsa::der::find_scalar_range", "size": 34}, {"crate": "bt4ot_embedded", "name": "crypto_bigint::traits::Zero::is_zero", "size": 34}, {"crate": "std", "name": "core::iter::traits::iterator::Iterator::nth", "size": 34}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 34}, {"crate": "der?", "name": "::eq", "size": 32}, {"crate": "hash32", "name": "::write", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Address::from_bytes", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::dst_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::src_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&T>::data", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::dst_addr", "size": 32}, {"crate": "std", "name": "core::ops::range::RangeInclusive::contains", "size": 32}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 32}, {"crate": "const_oid", "name": "const_oid::ObjectIdentifier::as_bytes", "size": 32}, {"crate": "sec1", "name": "sec1::point::Tag::from_u8", "size": 32}, {"crate": "heapless", "name": "heapless::deque::Deque::len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 32}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_writen::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read::{{closure}}>", "size": 32}, {"crate": "portable_atomic", "name": "portable_atomic::AtomicBool::compare_exchange", "size": 32}, {"crate": "embassy_time", "name": "embassy_time::timer::Timer::after_millis", "size": 32}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::multi_waker::MultiWakerRegistration<_>::wake", "size": 32}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::exec_jmp", "size": 32}, {"crate": "der?", "name": "::eq", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::combine", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Cidr::contains_addr", "size": 30}, {"crate": "p256", "name": "::double", "size": 30}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::square", "size": 30}, {"crate": "no_std_net", "name": "core::slice::::copy_from_slice", "size": 30}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 30}, {"crate": "std", "name": "core::panicking::panic_fmt", "size": 30}, {"crate": "const_oid", "name": "<[A] as core::slice::cmp::SlicePartialEq>::equal", "size": 30}, {"crate": "bt4ot_embedded", "name": "elliptic_curve::scalar::nonzero::NonZeroScalar::new", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_readn::{{closure}}>", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::log_init::{{closure}}>", "size": 30}, {"crate": "tuf_no_std", "name": " as tuf_no_std::role::SignedFile>::get_signatures", "size": 28}, {"crate": "tuf_no_std", "name": "tuf_no_std::signature::::raw_sig", "size": 28}, {"crate": "tuf_no_std", "name": "::decode_value", "size": 28}, {"crate": "tuf_no_std", "name": "::decode_value", "size": 28}, {"crate": "tuf_no_std", "name": "::decode_value", "size": 28}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::advance", "size": 28}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 28}, {"crate": "pkcs8", "name": "::decode_value", "size": 28}, {"crate": "pkcs8", "name": "::decode_value", "size": 28}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 28}, {"crate": "p256", "name": "core::panicking::assert_failed", "size": 28}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::multiply", "size": 28}, {"crate": "p256", "name": "core::panicking::assert_failed", "size": 28}, {"crate": "critical_section", "name": "critical_section::with", "size": 28}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 28}, {"crate": "bt4ot_embedded", "name": "::decode_value", "size": 28}, {"crate": "p256?", "name": "::sub", "size": 28}, {"crate": "p256?", "name": ">::add", "size": 28}, {"crate": "p256?", "name": ">::mul", "size": 28}, {"crate": "p256?", "name": "::neg", "size": 28}, {"crate": "atomic_polyfill", "name": "atomic_polyfill::polyfill::AtomicU32::fetch_or", "size": 28}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 28}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std?", "name": "core::ptr::drop_in_place<> as tuf_no_std_common::remote::TufTransportAsync>::fetch_target_file::{{closure}}>", "size": 28}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::wait_for_event::{{closure}}>", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place>::fetch_role::{{closure}}>", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place::{{closure}}>", "size": 28}, {"crate": "std", "name": "core::result::Result::and_then", "size": 26}, {"crate": "digest", "name": " as digest::Update>::update::{{closure}}", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_idx_unchecked", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::accept_digit", "size": 26}, {"crate": "pkcs8", "name": "der::encode::EncodeValue::header", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "const_oid?", "name": "::eq", "size": 26}, {"crate": "serde_json_core?", "name": "::next_value_seed", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "x509_cert", "name": " as der::reader::Reader>::peek_byte", "size": 24}, {"crate": "x509_cert", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 24}, {"crate": "tuf_no_std_der", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 24}, {"crate": "tuf_no_std_der", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 24}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::peek_byte", "size": 24}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::peek_byte", "size": 24}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 24}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 24}, {"crate": "spki", "name": "core::iter::adapters::zip::TrustedRandomAccessNoCoerce::size", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_code", "size": 24}, {"crate": "smoltcp?", "name": "::fmt", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::protocol_len", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::hardware_len", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::hop_limit", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame<&T>::payload", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::peek", "size": 24}, {"crate": "pem_rfc7468", "name": "base64ct::decoder::Decoder::is_finished", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::is_odd", "size": 24}, {"crate": "embassy_rp", "name": "embassy_rp::reset::unreset_wait", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::Executor::spawn", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 24}, {"crate": "defmt", "name": "defmt::export::str", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "defmt", "name": "core::fmt::Write::write_fmt", "size": 24}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::payload_mut", "size": 24}, {"crate": "embassy_net_driver_channel?", "name": "::fmt", "size": 24}, {"crate": "embassy_executor?", "name": "::fmt", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 24}, {"crate": "std", "name": "core::ptr::drop_in_place>>::{{closure}}>", "size": 24}, {"crate": "std?", "name": "core::ptr::drop_in_place<> as tuf_no_std_common::remote::TufTransportAsync>::fetch_targets::{{closure}}>", "size": 24}, {"crate": "std?", "name": "core::ptr::drop_in_place<> as tuf_no_std_common::remote::TufTransportAsync>::fetch_root::{{closure}}>", "size": 24}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"name": "__aeabi_ldivmod", "size": 22}, {"name": "__aeabi_uldivmod", "size": 22}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 22}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::wake", "size": 22}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::wake", "size": 22}, {"crate": "der", "name": "der::asn1::integer::uint::::value_len", "size": 22}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::nonblocking_write", "size": 22}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"name": "HardFaultTrampoline", "size": 20}, {"crate": "x509_cert", "name": "::encoded_len", "size": 20}, {"crate": "std", "name": "::next", "size": 20}, {"crate": "pkcs8", "name": "::encoded_len", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_neg", "size": 20}, {"crate": "hash32", "name": "<&T as core::fmt::Debug>::fmt", "size": 20}, {"crate": "embassy_rp", "name": "::now", "size": 20}, {"crate": "der", "name": "der::asn1::integer::uint::::encode_value", "size": 20}, {"crate": "std", "name": "core::option::unwrap_failed", "size": 20}, {"crate": "std", "name": "core::fmt::Write::write_fmt", "size": 20}, {"crate": "heapless", "name": "heapless::indexmap::IndexMap::contains_key", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_set_window::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::check_status::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_is_up::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write32::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read8::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write16::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::writen::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::readn::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read8::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "<() as core::fmt::Debug>::fmt", "size": 20}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 20}, {"crate": "std", "name": "__udivmodsi4", "size": 18}, {"name": "__aeabi_uidivmod", "size": 18}, {"crate": "std", "name": "__udivmoddi4", "size": 18}, {"crate": "tuf_no_std_der", "name": "sec1::point::EncodedPoint::is_identity", "size": 18}, {"crate": "tuf_no_std_common", "name": "der::writer::Writer::write_byte", "size": 18}, {"crate": "spki", "name": " as core::default::Default>::default", "size": 18}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::config_changed", "size": 18}, {"crate": "der", "name": "der::writer::Writer::write_byte", "size": 18}, {"crate": "der", "name": "der::asn1::bit_string::BitStringRef::as_bytes", "size": 18}, {"crate": "der", "name": "der::asn1::integer::uint::::value_len", "size": 18}, {"crate": "der", "name": "der::asn1::integer::uint::::value_len", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u64", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u8", "size": 18}, {"crate": "defmt", "name": "defmt::export::istr", "size": 18}, {"crate": "benchmark_code_size_full", "name": "benchmark_code_size_full::__cortex_m_rt_main", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}},cyw43::bus::Bus>::init::{{closure}}::{{closure}}>>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::wait_for_event::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::cmd_read::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::write::{{closure}}>", "size": 18}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset", "size": 16}, {"crate": "tuf_no_std", "name": ">::spec_eq", "size": 16}, {"crate": "tuf_no_std", "name": "<&T as core::fmt::Debug>::fmt", "size": 16}, {"crate": "tuf_no_std", "name": " as der::reader::Reader>::offset", "size": 16}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::buffer_len", "size": 16}, {"crate": "smoltcp", "name": ">::add_assign", "size": 16}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 16}, {"crate": "pkcs8", "name": "<&T as core::fmt::Debug>::fmt", "size": 16}, {"crate": "pem_rfc7468", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "p256", "name": "::eq", "size": 16}, {"crate": "p256", "name": "ff::Field::is_zero", "size": 16}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::pop_index", "size": 16}, {"crate": "embassy_executor", "name": "defmt::export::fmt", "size": 16}, {"crate": "der", "name": "der::tag::Tag::is_constructed", "size": 16}, {"crate": "der", "name": "der::asn1::bit_string::BitStringRef::from_bytes", "size": 16}, {"crate": "der", "name": ">::spec_eq", "size": 16}, {"crate": "defmt", "name": "defmt::encoding::inner::Encoder::write::{{closure}}", "size": 16}, {"crate": "defmt", "name": "defmt::export::integers::u32", "size": 16}, {"crate": "defmt", "name": "::write_str", "size": 16}, {"crate": "defmt", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "std", "name": "::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "bt4ot_embedded", "name": "sec1::point::EncodedPoint::is_identity", "size": 16}, {"crate": "subtle?", "name": "::not", "size": 16}, {"crate": "embassy_net?", "name": " as smoltcp::phy::Device>::capabilities", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 16}, {"crate": "sha2?", "name": "::finalize_variable_core::{{closure}}", "size": 14}, {"crate": "std", "name": "core::fmt::Formatter::write_fmt", "size": 14}, {"crate": "pkcs8", "name": "der::encode::EncodeValue::header", "size": 14}, {"crate": "p256", "name": "p256::arithmetic::scalar::Scalar::is_even", "size": 14}, {"crate": "p256", "name": " as crypto_bigint::traits::Integer>::is_odd", "size": 14}, {"crate": "embassy_time", "name": "_defmt_timestamp", "size": 14}, {"crate": "der", "name": "der::tag::Tag::number", "size": 14}, {"crate": "defmt", "name": "defmt::export::header", "size": 14}, {"crate": "crypto_bigint", "name": "crypto_bigint::ct_choice:: for subtle::Choice>::from", "size": 14}, {"crate": "std", "name": "core::str::slice_error_fail", "size": 14}, {"name": "__Thumbv6MABSLongThunk__ZN10embassy_rp5flash11ram_helpers17write_flash_inner17h82c1bb1a60c73aa7E", "size": 12}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memclr8", "size": 12}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memclr", "size": 12}, {"crate": "x509_cert", "name": " as der::encode::EncodeValue>::value_len", "size": 12}, {"crate": "tuf_no_std_der", "name": "::encoded_len", "size": 12}, {"crate": "spki", "name": " as generic_array::sequence::GenericSequence>::generate", "size": 12}, {"crate": "spki", "name": " as generic_array::sequence::GenericSequence>::generate", "size": 12}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::push_index", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_0", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_1", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_2", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_3", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::clone", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::SyncExecutor::alarm_callback", "size": 12}, {"crate": "der", "name": "::value_len", "size": 12}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 12}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 12}, {"crate": "crypto_bigint", "name": "subtle::black_box", "size": 12}, {"crate": "bt4ot_embedded", "name": "<&T as core::fmt::Debug>::fmt", "size": 12}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::encode_value", "size": 12}, {"name": "__aeabi_memmove8", "size": 10}, {"name": "__aeabi_memmove", "size": 10}, {"name": "__aeabi_memcpy8", "size": 10}, {"name": "__aeabi_memmove4", "size": 10}, {"name": "__aeabi_memclr4", "size": 10}, {"name": "__aeabi_memcpy4", "size": 10}, {"name": "__aeabi_memcpy", "size": 10}, {"name": "__aeabi_uidiv", "size": 10}, {"name": "__aeabi_memclr", "size": 10}, {"name": "memcmp", "size": 10}, {"name": "__aeabi_memset4", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy", "size": 10}, {"name": "__aeabi_memclr8", "size": 10}, {"crate": "pkcs8", "name": "::encoded_len", "size": 10}, {"crate": "der", "name": "der::tag:: for u8>::from", "size": 10}, {"crate": "defmt", "name": "defmt::export::bool", "size": 10}, {"crate": "std", "name": "core::cmp::impls:: for &A>::eq", "size": 10}, {"crate": "x509_cert", "name": "x509_cert::time::Time::to_unix_duration", "size": 8}, {"crate": "defmt", "name": "defmt::default_panic::panic_cold_explicit", "size": 8}, {"crate": "defmt", "name": "__defmt_default_panic", "size": 8}, {"name": "main", "size": 8}, {"crate": "cortex_m_rt", "name": "HardFault_", "size": 2}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::drop", "size": 2}, {"crate": "defmt", "name": "__defmt_default_timestamp", "size": 2}, {"crate": "cortex_m_rt", "name": "DefaultHandler_", "size": 2}]}, "cargo_bloat_crates": {"file-size": 2393100, "text-section-size": 238104, "crates": [{"name": "tuf_no_std", "size": 52190}, {"name": "benchmark_code_size_full", "size": 37346}, {"name": "bt4ot_embedded", "size": 26686}, {"name": "smoltcp", "size": 25114}, {"name": "std", "size": 15832}, {"name": "p256", "size": 13236}, {"name": "cyw43", "size": 10212}, {"name": "embassy_executor", "size": 8938}, {"name": "der", "size": 7610}, {"name": "pem_rfc7468", "size": 5458}, {"name": "pkcs8", "size": 4796}, {"name": "sha2", "size": 3922}, {"name": "tuf_no_std_der", "size": 3656}, {"name": "embassy_net", "size": 3182}, {"name": "x509_cert", "size": 2826}, {"name": "tuf_no_std_common", "size": 2052}, {"name": "bt4ot_ota_common", "size": 2008}, {"name": "spki", "size": 1796}, {"name": "embassy_rp", "size": 1336}, {"name": "embassy_embedded_hal", "size": 1232}, {"name": "const_oid", "size": 1138}, {"name": "serde_json_core", "size": 1012}, {"name": "cyw43_pio", "size": 760}, {"name": "defmt", "size": 758}, {"name": "embassy_sync", "size": 622}, {"name": "defmt_rtt", "size": 466}, {"name": "[Unknown]", "size": 432}, {"name": "ecdsa", "size": 346}, {"name": "heapless", "size": 340}, {"name": "sec1", "size": 304}, {"name": "panic_probe", "size": 276}, {"name": "pio", "size": 244}, {"name": "crypto_bigint", "size": 228}, {"name": "base64ct", "size": 228}, {"name": "embassy_boot", "size": 198}, {"name": "embassy_time", "size": 134}, {"name": "embedded_io_async", "size": 132}, {"name": "byteorder", "size": 120}, {"name": "generic_array", "size": 88}, {"name": "serde", "size": 88}, {"name": "hex", "size": 72}, {"name": "subtle", "size": 68}, {"name": "embassy_net_driver_channel", "size": 60}, {"name": "hash32", "size": 52}, {"name": "embassy_hal_internal", "size": 48}, {"name": "cortex_m_rt", "size": 44}, {"name": "embassy_net_driver", "size": 36}, {"name": "fixed", "size": 36}, {"name": "cortex_m", "size": 36}, {"name": "portable_atomic", "size": 32}, {"name": "no_std_net", "size": 30}, {"name": "atomic_polyfill", "size": 28}, {"name": "critical_section", "size": 28}, {"name": "digest", "size": 26}]}}, "benchmark-code-size-no-tuf": {"raw_size": 194312, "cargo_bloat": {"file-size": 1773272, "text-section-size": 168980, "functions": [{"crate": "benchmark_code_size_no_tuf", "name": "benchmark_code_size_no_tuf::____embassy_main_task::{{closure}}", "size": 27864}, {"crate": "bt4ot_embedded", "name": "der::reader::Reader::read_nested", "size": 13320}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ethernet::::process_ethernet", "size": 9676}, {"crate": "p256", "name": "::mul", "size": 5526}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 5452}, {"crate": "cyw43", "name": "cyw43::runner::Runner::check_status::{{closure}}", "size": 4320}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::dispatch_ip", "size": 4264}, {"crate": "bt4ot_embedded", "name": "der::reader::Reader::decode", "size": 3884}, {"crate": "sha2", "name": "sha2::sha256::compress256", "size": 3440}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 2528}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_mul", "size": 2422}, {"crate": "bt4ot_embedded", "name": "bt4ot_embedded::verify_signature", "size": 2396}, {"crate": "pem_rfc7468", "name": "pem_rfc7468::decoder::decode", "size": 1996}, {"crate": "bt4ot_embedded", "name": "der::reader::Reader::decode", "size": 1820}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::montgomery_reduce", "size": 1670}, {"crate": "pem_rfc7468", "name": "::decode", "size": 1596}, {"crate": "pem_rfc7468", "name": "base64ct::decoder::Decoder::perform_decode", "size": 1556}, {"crate": "std", "name": "core::fmt::Formatter::pad", "size": 1536}, {"crate": "spki", "name": "spki::algorithm::AlgorithmIdentifier::assert_oids", "size": 1294}, {"crate": "pkcs8", "name": "der::reader::Reader::decode", "size": 1184}, {"crate": "std", "name": "core::char::methods::::escape_debug_ext", "size": 1020}, {"crate": "tuf_no_std_der", "name": " as core::convert::TryFrom>>::try_from", "size": 1000}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u64_div_rem", "size": 938}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::write", "size": 828}, {"crate": "der", "name": "der::datetime::DateTime::from_unix_duration", "size": 820}, {"crate": "std", "name": "compiler_builtins::mem::memmove", "size": 788}, {"crate": "bt4ot_embedded", "name": "der::asn1::optional::>::der_cmp", "size": 778}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::parse_ack", "size": 728}, {"crate": "std", "name": "core::str::slice_error_fail_rt", "size": 668}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::poll_at::{{closure}}", "size": 650}, {"crate": "der", "name": "der::datetime::DateTime::new", "size": 648}, {"crate": "bt4ot_embedded", "name": "bt4ot_embedded::parse_cert", "size": 632}, {"crate": "bt4ot_embedded", "name": "der::asn1::context_specific::ContextSpecific::decode_implicit", "size": 616}, {"crate": "der?", "name": "::fmt", "size": 612}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 608}, {"crate": "std", "name": "::write_str", "size": 604}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::write::{{closure}}", "size": 584}, {"crate": "bt4ot_embedded", "name": ">::add", "size": 560}, {"crate": "std", "name": "core::str::converts::from_utf8", "size": 552}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::connect::{{closure}}", "size": 552}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Repr::emit", "size": 544}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::read::{{closure}}", "size": 532}, {"crate": "bt4ot_embedded", "name": "primeorder::projective::ProjectivePoint::mul", "size": 500}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Repr::emit", "size": 500}, {"crate": "p256", "name": "p256::arithmetic::scalar::scalar_impl::subtract_n_if_necessary", "size": 488}, {"crate": "der", "name": "::decode", "size": 484}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral", "size": 478}, {"crate": "der", "name": "<&T as core::fmt::Display>::fmt", "size": 472}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::sub_inner", "size": 464}, {"crate": "x509_cert", "name": "::decode", "size": 456}, {"crate": "x509_cert", "name": "::decode", "size": 456}, {"crate": "x509_cert", "name": "::decode", "size": 456}, {"crate": "pkcs8", "name": "::decode", "size": 456}, {"crate": "pkcs8", "name": "::decode", "size": 456}, {"crate": "bt4ot_embedded", "name": "::encode", "size": 456}, {"crate": "bt4ot_embedded", "name": ">::double", "size": 448}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 440}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 440}, {"crate": "serde_json_core?", "name": "<&mut serde_json_core::de::Deserializer as serde::de::Deserializer>::deserialize_ignored_any", "size": 438}, {"crate": "bt4ot_embedded", "name": "::encode", "size": 436}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_reset::{{closure}}", "size": 428}, {"crate": "embassy_net", "name": "embassy_net::Inner::apply_static_config", "size": 428}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::TcpOption::emit", "size": 412}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}", "size": 408}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_disable::{{closure}}", "size": 400}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::run", "size": 400}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar::{{closure}}", "size": 396}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u32_div_rem", "size": 392}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write::{{closure}}", "size": 384}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read::{{closure}}", "size": 376}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::new", "size": 360}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}", "size": 352}, {"crate": "cyw43", "name": "cyw43::runner::Runner::handle_irq::{{closure}}", "size": 344}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_add", "size": 340}, {"crate": "std", "name": "compiler_builtins::mem::memcpy", "size": 334}, {"crate": "const_oid", "name": "const_oid::arcs::Arcs::try_next", "size": 332}, {"crate": "std", "name": "core::fmt::write", "size": 330}, {"crate": "der", "name": ">::try_from", "size": 324}, {"crate": "std", "name": "core::fmt::builders::DebugStruct::field", "size": 324}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::encode_value", "size": 320}, {"crate": "ecdsa", "name": "::decode", "size": 312}, {"crate": "x509_cert", "name": "der::reader::Reader::decode", "size": 304}, {"crate": "x509_cert", "name": " as der::reader::Reader>::read_into", "size": 298}, {"crate": "cyw43", "name": "cyw43::control::Control::get_iovar::{{closure}}", "size": 292}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_set_window::{{closure}}", "size": 288}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_reply", "size": 280}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl::{{closure}}", "size": 272}, {"crate": "std", "name": "core::unicode::printable::check", "size": 260}, {"crate": "tuf_no_std_der", "name": " as elliptic_curve::point::DecompressPoint>::decompress", "size": 256}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 256}, {"crate": "bt4ot_embedded", "name": "der::asn1::optional::>::encode", "size": 254}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 248}, {"crate": "pio", "name": "pio::InstructionOperands::encode", "size": 244}, {"crate": "p256", "name": "::sqrt", "size": 244}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 244}, {"crate": "sha2", "name": "sha2::sha256::soft::schedule", "size": 238}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 236}, {"crate": "der", "name": "::der_cmp", "size": 232}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wlan_write::{{closure}}", "size": 232}, {"crate": "sha2", "name": "sha2::sha256::soft::sha256_digest_round_x2", "size": 230}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wait_for_event::{{closure}}", "size": 224}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reset", "size": 222}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_broadcast_v4", "size": 220}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup", "size": 220}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::erase", "size": 216}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::remove_default_ipv4_route", "size": 204}, {"crate": "const_oid?", "name": "::fmt", "size": 204}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32x2::{{closure}}", "size": 204}, {"crate": "smoltcp", "name": "heapless::linear_map::LinearMap::insert", "size": 196}, {"crate": "std", "name": "core::fmt::Formatter::debug_tuple_field1_finish", "size": 196}, {"crate": "cyw43", "name": "cyw43::bus::Bus::readn::{{closure}}", "size": 196}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::flush::{{closure}}", "size": 196}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::rst_reply", "size": 190}, {"crate": "p256", "name": "::add_assign", "size": 188}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ipv4::::icmpv4_reply", "size": 186}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Repr::parse", "size": 184}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_str", "size": 184}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_writen::{{closure}}", "size": 184}, {"crate": "base64ct", "name": "::next", "size": 182}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::seq_to_transmit", "size": 180}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 180}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 176}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 176}, {"crate": "embassy_rp", "name": "embassy_rp::pio::Common::try_load_program_at", "size": 176}, {"crate": "std", "name": "core::cmp::PartialEq::ne", "size": 172}, {"crate": "bt4ot_embedded", "name": "::value_len", "size": 172}, {"crate": "const_oid", "name": "<&T as core::fmt::Display>::fmt", "size": 168}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_readn::{{closure}}", "size": 168}, {"crate": "der", "name": "::read_slice", "size": 166}, {"crate": "std", "name": "compiler_builtins::mem::memset", "size": 160}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::add::put", "size": 158}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_many", "size": 156}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::data", "size": 152}, {"crate": "panic_probe", "name": "<&T as core::fmt::Display>::fmt", "size": 152}, {"crate": "der", "name": "der::tag::Tag::octet", "size": 152}, {"crate": "std", "name": "core::panicking::assert_failed_inner", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 152}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::iface::neighbor::Cache::lookup", "size": 148}, {"crate": "embassy_net", "name": "::drop", "size": 148}, {"crate": "der", "name": "::encode", "size": 148}, {"crate": "cyw43", "name": "cyw43::bus::Bus::writen::{{closure}}", "size": 148}, {"crate": "tuf_no_std_common", "name": "<&T as core::fmt::Debug>::fmt", "size": 144}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::next_header", "size": 144}, {"crate": "pkcs8", "name": "::encode", "size": 144}, {"crate": "const_oid", "name": "const_oid::ObjectIdentifier::from_bytes", "size": 144}, {"crate": "bt4ot_embedded", "name": "::value_len", "size": 144}, {"crate": "bt4ot_embedded", "name": "<&T as core::fmt::Debug>::fmt", "size": 144}, {"crate": "defmt_rtt", "name": "_defmt_write", "size": 140}, {"crate": "sec1?", "name": "::fmt", "size": 136}, {"crate": "p256", "name": ">::sub_assign", "size": 136}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 136}, {"crate": "sec1?", "name": "::fmt", "size": 136}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write32_swapped::{{closure}}", "size": 136}, {"crate": "pkcs8", "name": "::decode_value", "size": 132}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm", "size": 132}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32_swapped::{{closure}}", "size": 132}, {"crate": "embedded_io_async", "name": "embedded_io_async::Read::read_exact::{{closure}}", "size": 132}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_function_pointers_with_boot2", "size": 128}, {"crate": "der", "name": "der::tag::Tag::assert_eq", "size": 128}, {"crate": "std", "name": "core::fmt::Formatter::debug_struct_field2_finish", "size": 128}, {"crate": "defmt", "name": "core::fmt::Write::write_char", "size": 126}, {"crate": "panic_probe", "name": "rust_begin_unwind", "size": 124}, {"crate": "cyw43", "name": "cyw43::control::Control::gpio_set::{{closure}}", "size": 124}, {"crate": "std", "name": "__divmoddi4", "size": 122}, {"crate": "pkcs8", "name": "::decode", "size": 122}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::Repr::buffer_len", "size": 120}, {"crate": "embassy_net", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 120}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 120}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 120}, {"crate": "const_oid", "name": "<&T as core::fmt::Debug>::fmt", "size": 120}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "x509_cert", "name": "::value_len", "size": 118}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::fill_checksum", "size": 116}, {"crate": "embassy_rp", "name": "IO_IRQ_BANK0", "size": 116}, {"crate": "std", "name": "core::fmt::Formatter::debug_struct_field1_finish", "size": 116}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 114}, {"crate": "tuf_no_std_common", "name": "<&T as core::fmt::Debug>::fmt", "size": 112}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_sub", "size": 112}, {"crate": "der", "name": "::write", "size": 112}, {"crate": "bt4ot_embedded", "name": "<&T as core::fmt::Debug>::fmt", "size": 112}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy4", "size": 110}, {"crate": "pkcs8", "name": "::encode", "size": 110}, {"crate": "bt4ot_embedded", "name": "der::datetime::encode_decimal", "size": 110}, {"crate": "embassy_executor", "name": "_embassy_time_schedule_wake", "size": 108}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl_set_u32::{{closure}}", "size": 108}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::advance_position", "size": 104}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::remove", "size": 104}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_unallocated", "size": 104}, {"crate": "embassy_rp", "name": "embassy_rp::time_driver::TimerDriver::check_alarm", "size": 104}, {"crate": "der", "name": "der::writer::slice::SliceWriter::encode", "size": 104}, {"crate": "embassy_boot?", "name": "::fmt", "size": 104}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::advance_position", "size": 102}, {"crate": "std", "name": "::fmt", "size": 102}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::advance_position", "size": 100}, {"crate": "embassy_rp", "name": "DMA_IRQ_0", "size": 100}, {"crate": "der", "name": "der::reader::Reader::read_into", "size": 100}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 100}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32::{{closure}}", "size": 100}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::advance_position", "size": 98}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset4", "size": 96}, {"crate": "smoltcp", "name": "::fmt", "size": 96}, {"crate": "pkcs8", "name": " as der::encode::EncodeValue>::value_len", "size": 96}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::advance_position", "size": 96}, {"crate": "pem_rfc7468", "name": "base64ct::alphabet::Alphabet::decode_6bits", "size": 96}, {"crate": "cyw43", "name": "cyw43::structs::BdcHeader::parse", "size": 96}, {"crate": "std", "name": "core::ptr::drop_in_place>>", "size": 96}, {"crate": "embassy_embedded_hal", "name": "embassy_embedded_hal::flash::partition::blocking::BlockingPartition::new", "size": 96}, {"crate": "embassy_boot", "name": "embassy_boot::firmware_updater::blocking::BlockingFirmwareState::get_state", "size": 94}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::new", "size": 92}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reply", "size": 92}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write16::{{closure}}", "size": 92}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::ReadNorFlash>::read", "size": 92}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::new", "size": 90}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::route", "size": 90}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_neighbor", "size": 90}, {"crate": "der", "name": "der::asn1::bit_string::BitStringRef::new", "size": 90}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::encode_value", "size": 90}, {"crate": "x509_cert", "name": "der::reader::nested::NestedReader::new", "size": 88}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 88}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup::{{closure}}", "size": 88}, {"crate": "p256", "name": ">>::reduce_bytes", "size": 88}, {"crate": "generic_array", "name": "generic_array::GenericArray::clone_from_slice", "size": 88}, {"crate": "serde", "name": "serde::de::MapAccess::next_value", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write32::{{closure}}", "size": 88}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::new", "size": 86}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::pseudo_header", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::challenge_ack_reply", "size": 84}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::new", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::register", "size": 84}, {"crate": "embassy_net", "name": "defmt::export::fmt", "size": 84}, {"crate": "defmt_rtt", "name": "_defmt_acquire", "size": 84}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::write_impl", "size": 84}, {"crate": "std", "name": "core::ops::function::FnMut::call_mut", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write8::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read8::{{closure}}", "size": 84}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Receiver::receive_done", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::send_done", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::new_checked", "size": 80}, {"crate": "smoltcp", "name": "::next", "size": 80}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::to_canonical", "size": 80}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_range_program", "size": 80}, {"crate": "std?", "name": " as serde::de::DeserializeSeed>::deserialize", "size": 80}, {"crate": "std?", "name": " as serde::de::DeserializeSeed>::deserialize", "size": 80}, {"crate": "std?", "name": " as serde::de::DeserializeSeed>::deserialize", "size": 80}, {"crate": "der", "name": "der::asn1::integer::uint::decode_to_slice", "size": 78}, {"crate": "x509_cert", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "x509_cert", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "tuf_no_std_common", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::DhcpOptionWriter::emit", "size": 76}, {"crate": "smoltcp", "name": "::fmt", "size": 76}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::header_len", "size": 76}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::array::>::from_be_byte_array", "size": 76}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "der?", "name": "::fmt", "size": 76}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 76}, {"name": "__aeabi_lmul", "size": 74}, {"crate": "pkcs8", "name": "::encode", "size": 74}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::to_bytes", "size": 74}, {"crate": "smoltcp", "name": "heapless::vec::Vec::clone", "size": 72}, {"crate": "embassy_rp", "name": "::acquire", "size": 72}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::set_timeout", "size": 72}, {"crate": "der", "name": "der::length::Length::initial_octet", "size": 72}, {"crate": "der", "name": "der::reader::slice::SliceReader::remaining", "size": 72}, {"crate": "defmt_rtt", "name": "_defmt_release", "size": 72}, {"crate": "hex", "name": "hex::val", "size": 72}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::poll_send", "size": 72}, {"crate": "std", "name": "__clzsi2", "size": 70}, {"crate": "der", "name": "der::length::Length::for_tlv", "size": 70}, {"crate": "std", "name": ">::into", "size": 68}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::in_same_network", "size": 68}, {"crate": "p256", "name": "crypto_bigint::uint::add::>::wrapping_add", "size": 68}, {"crate": "std", "name": "core::result::unwrap_failed", "size": 68}, {"crate": "std", "name": "::write_char", "size": 68}, {"crate": "bt4ot_embedded", "name": "sec1::point::EncodedPoint::tag", "size": 68}, {"crate": "x509_cert", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "x509_cert", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "x509_cert", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "pkcs8", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "pkcs8", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "der", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 64}, {"crate": "pkcs8", "name": "::encode", "size": 64}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::from_bytes", "size": 64}, {"crate": "p256", "name": ">::shr_assign", "size": 64}, {"crate": "p256", "name": " as subtle::ConditionallySelectable>::conditional_select", "size": 64}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 64}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 64}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::blocking_write", "size": 64}, {"crate": "const_oid", "name": "::next", "size": 64}, {"crate": "bt4ot_embedded", "name": "::decode_value", "size": 64}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 64}, {"crate": "x509_cert", "name": " as der::encode::EncodeValue>::value_len", "size": 62}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_ident", "size": 62}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::sqn", "size": 62}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral::write_prefix", "size": 62}, {"crate": "bt4ot_embedded", "name": " as der::encode::EncodeValue>::value_len", "size": 62}, {"crate": "std", "name": " as core::fmt::Debug>::fmt", "size": 60}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_one", "size": 60}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 60}, {"crate": "pkcs8", "name": "::encode_value", "size": 60}, {"crate": "std", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 60}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 60}, {"crate": "std", "name": "core::slice::::copy_from_slice::len_mismatch_fail", "size": 60}, {"crate": "std", "name": " as core::fmt::Debug>::fmt", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_index_order_fail", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_end_index_len_fail", "size": 60}, {"crate": "std", "name": "core::panicking::panic_bounds_check", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_start_index_len_fail", "size": 60}, {"crate": "std", "name": "core::ptr::drop_in_place>::init::{{closure}}>", "size": 60}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::wake", "size": 58}, {"crate": "bt4ot_embedded", "name": "der::asn1::optional::>::encoded_len", "size": 58}, {"crate": "serde_json_core?", "name": "<&mut serde_json_core::de::Deserializer as serde::de::Deserializer>::deserialize_str", "size": 58}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 58}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_ip_addr", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "embassy_executor", "name": "__pender", "size": 56}, {"crate": "std", "name": "core::panicking::panic_explicit", "size": 56}, {"crate": "bt4ot_embedded", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "bt4ot_embedded", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "bt4ot_embedded", "name": "der::encode::EncodeValue::header", "size": 56}, {"crate": "bt4ot_embedded", "name": "elliptic_curve::scalar::primitive::ScalarPrimitive::from_slice", "size": 56}, {"name": "PIO0_IRQ_0", "size": 56}, {"crate": "heapless", "name": "heapless::deque::Deque::pop_front", "size": 56}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 56}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 56}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::try_send", "size": 56}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::register", "size": 54}, {"crate": "der", "name": "der::error::Error::nested", "size": 54}, {"crate": "tuf_no_std_common", "name": "sec1::point::EncodedPoint::tag", "size": 52}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet<&T>::payload", "size": 52}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_whitespace", "size": 52}, {"crate": "embassy_time", "name": "::poll", "size": 52}, {"crate": "embassy_rp", "name": "__pre_init", "size": 52}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm_callback", "size": 52}, {"crate": "embassy_net", "name": "embassy_net::to_smoltcp_hardware_address", "size": 52}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::new", "size": 52}, {"crate": "der", "name": "::fmt", "size": 52}, {"crate": "std", "name": "core::slice::::split_at_mut", "size": 52}, {"crate": "std", "name": "core::option::expect_failed", "size": 52}, {"crate": "std", "name": "core::slice::::split_at", "size": 52}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_reset::{{closure}}>", "size": 52}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::register", "size": 50}, {"crate": "pem_rfc7468", "name": "pem_rfc7468::grammar::strip_trailing_eol", "size": 50}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Address::from_bytes", "size": 48}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 48}, {"crate": "smoltcp", "name": "defmt::traits::Format::_format_data", "size": 48}, {"crate": "smoltcp", "name": "::sub", "size": 48}, {"crate": "smoltcp", "name": ">::add", "size": 48}, {"crate": "serde_json_core", "name": "::unit_variant", "size": 48}, {"crate": "pem_rfc7468", "name": "base64ct::decoder::Decoder::advance_line", "size": 48}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::Uint<_>::from_words", "size": 48}, {"crate": "embassy_hal_internal", "name": "embassy_hal_internal::interrupt::InterruptExt::set_priority", "size": 48}, {"crate": "der", "name": "der::asn1::integer::uint::encoded_len", "size": 48}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_mutably_borrowed", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_borrowed", "size": 48}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 48}, {"crate": "bt4ot_embedded", "name": "ecdsa::der::Signature::s", "size": 48}, {"crate": "bt4ot_embedded", "name": "ecdsa::der::Signature::r", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_src_addr", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_dst_addr", "size": 48}, {"crate": "tuf_no_std_common?", "name": "::fmt", "size": 48}, {"crate": "embassy_sync?", "name": "::fmt", "size": 48}, {"crate": "std", "name": "compiler_builtins::mem::memcmp", "size": 46}, {"crate": "x509_cert", "name": " as der::reader::Reader>::peek_byte", "size": 46}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::end_map", "size": 46}, {"crate": "base64ct", "name": "base64ct::decoder::Line::trim_end", "size": 46}, {"name": "__aeabi_llsl", "size": 44}, {"crate": "byteorder", "name": "::write_u32", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::msg_type", "size": 44}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 44}, {"crate": "defmt", "name": "defmt::impls::primitives::::_format_data", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_to_transmit", "size": 44}, {"crate": "pkcs8", "name": "::encoded_len", "size": 44}, {"crate": "std", "name": "core::result::Result::and_then", "size": 44}, {"crate": "embassy_rp", "name": "_embassy_time_allocate_alarm", "size": 44}, {"crate": "bt4ot_embedded", "name": "::encoded_len", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_ethertype", "size": 44}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::new_checked", "size": 42}, {"crate": "der", "name": "der::length:: for core::result::Result>::add", "size": 42}, {"crate": "der", "name": "core::slice::cmp::::cmp", "size": 42}, {"crate": "heapless?", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_disable::{{closure}}>", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::handle_irq::{{closure}}>", "size": 42}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&mut T>::data_mut", "size": 40}, {"crate": "pem_rfc7468", "name": "pem_rfc7468::decoder::check_for_headers", "size": 40}, {"crate": "p256", "name": "core::panicking::assert_failed", "size": 40}, {"crate": "p256", "name": "core::panicking::assert_failed", "size": 40}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::cmp::>::ct_lt", "size": 40}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 40}, {"crate": "der", "name": "::peek_byte", "size": 40}, {"crate": "der", "name": "der::asn1::integer::uint::strip_leading_zeroes", "size": 40}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_rem_by_zero", "size": 40}, {"crate": "const_oid", "name": ">::try_from", "size": 40}, {"crate": "subtle", "name": "subtle::CtOption::unwrap", "size": 40}, {"crate": "byteorder", "name": "::write_u16", "size": 40}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 40}, {"crate": "cortex_m_rt", "name": "Reset", "size": 40}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Timer::should_keep_alive", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::contiguous_window", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_unicast_v4", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::window_to_update", "size": 38}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::parse_object_colon", "size": 38}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe32_to_fe64", "size": 38}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::cmp::>::ct_eq", "size": 38}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::wake", "size": 38}, {"crate": "const_oid", "name": "<&T as core::fmt::Debug>::fmt", "size": 38}, {"crate": "embassy_net?", "name": "::clone", "size": 38}, {"name": "__aeabi_idiv", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_ident", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_type", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::payload_mut", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_ident", "size": 36}, {"crate": "byteorder", "name": "::read_u16", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::src_port", "size": 36}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::reset", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::total_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::scaled_window", "size": 36}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::end", "size": 36}, {"crate": "pkcs8", "name": " as der::reader::Reader>::peek_byte", "size": 36}, {"crate": "pem_rfc7468", "name": "core::slice::::starts_with", "size": 36}, {"crate": "embassy_time", "name": ">::add", "size": 36}, {"crate": "embassy_rp", "name": "::release", "size": 36}, {"crate": "embassy_net_driver", "name": "defmt::export::fmt_array", "size": 36}, {"crate": "der", "name": "::add", "size": 36}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 36}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 36}, {"crate": "std", "name": "::fmt", "size": 36}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_async_fn_resumed", "size": 36}, {"crate": "std", "name": "core::panicking::panic", "size": 36}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 36}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 36}, {"crate": "der?", "name": "::fmt", "size": 36}, {"crate": "embassy_net_driver_channel?", "name": " as embassy_net_driver::Driver>::hardware_address", "size": 36}, {"crate": "cortex_m", "name": "cortex_m::peripheral::scb::::sys_reset", "size": 36}, {"crate": "embassy_rp?", "name": " as core::future::future::Future>::poll", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 36}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 36}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::set_pindir", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::set_state", "size": 34}, {"crate": "ecdsa", "name": "ecdsa::der::find_scalar_range", "size": 34}, {"crate": "bt4ot_embedded", "name": "crypto_bigint::traits::Zero::is_zero", "size": 34}, {"crate": "std", "name": "core::iter::traits::iterator::Iterator::nth", "size": 34}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 34}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Address::from_bytes", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::dst_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::src_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&T>::data", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::dst_addr", "size": 32}, {"crate": "std", "name": "core::ops::range::RangeInclusive::contains", "size": 32}, {"crate": "const_oid", "name": "const_oid::ObjectIdentifier::as_bytes", "size": 32}, {"crate": "sec1", "name": "sec1::point::Tag::from_u8", "size": 32}, {"crate": "heapless", "name": "heapless::deque::Deque::len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 32}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_writen::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read::{{closure}}>", "size": 32}, {"crate": "portable_atomic", "name": "portable_atomic::AtomicBool::compare_exchange", "size": 32}, {"crate": "embassy_time", "name": "embassy_time::timer::Timer::after_millis", "size": 32}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::multi_waker::MultiWakerRegistration<_>::wake", "size": 32}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::exec_jmp", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::combine", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Cidr::contains_addr", "size": 30}, {"crate": "p256", "name": "::double", "size": 30}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::square", "size": 30}, {"crate": "no_std_net", "name": "core::slice::::copy_from_slice", "size": 30}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 30}, {"crate": "std", "name": "core::panicking::panic_fmt", "size": 30}, {"crate": "const_oid", "name": "<[A] as core::slice::cmp::SlicePartialEq>::equal", "size": 30}, {"crate": "bt4ot_embedded", "name": "elliptic_curve::scalar::nonzero::NonZeroScalar::new", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_readn::{{closure}}>", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::log_init::{{closure}}>", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::advance", "size": 28}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 28}, {"crate": "pkcs8", "name": "::decode_value", "size": 28}, {"crate": "pkcs8", "name": "::decode_value", "size": 28}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 28}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::multiply", "size": 28}, {"crate": "critical_section", "name": "critical_section::with", "size": 28}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 28}, {"crate": "bt4ot_embedded", "name": "::decode_value", "size": 28}, {"crate": "p256?", "name": "::sub", "size": 28}, {"crate": "std", "name": ">::into", "size": 28}, {"crate": "p256?", "name": ">::add", "size": 28}, {"crate": "p256?", "name": ">::mul", "size": 28}, {"crate": "p256?", "name": "::neg", "size": 28}, {"crate": "atomic_polyfill", "name": "atomic_polyfill::polyfill::AtomicU32::fetch_or", "size": 28}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 28}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::wait_for_event::{{closure}}>", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place::{{closure}}>", "size": 28}, {"crate": "x509_cert", "name": "der::encode::EncodeValue::header", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_idx_unchecked", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::accept_digit", "size": 26}, {"crate": "std", "name": "core::result::Result::and_then", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "const_oid?", "name": "::eq", "size": 26}, {"crate": "digest", "name": " as digest::Update>::update::{{closure}}", "size": 26}, {"crate": "serde_json_core?", "name": "::next_value_seed", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "x509_cert", "name": " as der::reader::Reader>::peek_byte", "size": 24}, {"crate": "x509_cert", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_code", "size": 24}, {"crate": "smoltcp?", "name": "::fmt", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::protocol_len", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::hardware_len", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::hop_limit", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame<&T>::payload", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "serde_json_core", "name": "serde_json_core::de::Deserializer::peek", "size": 24}, {"crate": "pem_rfc7468", "name": "base64ct::decoder::Decoder::is_finished", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::is_odd", "size": 24}, {"crate": "embassy_rp", "name": "embassy_rp::reset::unreset_wait", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::Executor::spawn", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 24}, {"crate": "defmt", "name": "defmt::export::str", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "defmt", "name": "core::fmt::Write::write_fmt", "size": 24}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::payload_mut", "size": 24}, {"crate": "embassy_net_driver_channel?", "name": "::fmt", "size": 24}, {"crate": "embassy_executor?", "name": "::fmt", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 24}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index", "size": 24}, {"name": "__aeabi_ldivmod", "size": 22}, {"name": "__aeabi_uldivmod", "size": 22}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 22}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::wake", "size": 22}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::wake", "size": 22}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::nonblocking_write", "size": 22}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"name": "HardFaultTrampoline", "size": 20}, {"crate": "x509_cert", "name": "::encoded_len", "size": 20}, {"crate": "std", "name": "::next", "size": 20}, {"crate": "pkcs8", "name": "::encoded_len", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_neg", "size": 20}, {"crate": "hash32", "name": "<&T as core::fmt::Debug>::fmt", "size": 20}, {"crate": "embassy_rp", "name": "::now", "size": 20}, {"crate": "std", "name": "core::option::unwrap_failed", "size": 20}, {"crate": "std", "name": "core::fmt::Write::write_fmt", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_set_window::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::check_status::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_is_up::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write32::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read8::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write16::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::writen::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::readn::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read8::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "<() as core::fmt::Debug>::fmt", "size": 20}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 20}, {"crate": "std", "name": "__udivmodsi4", "size": 18}, {"name": "__aeabi_uidivmod", "size": 18}, {"crate": "std", "name": "__udivmoddi4", "size": 18}, {"crate": "tuf_no_std_der", "name": "sec1::point::EncodedPoint::is_identity", "size": 18}, {"crate": "spki", "name": " as core::default::Default>::default", "size": 18}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::config_changed", "size": 18}, {"crate": "der", "name": "der::writer::Writer::write_byte", "size": 18}, {"crate": "der", "name": "der::asn1::bit_string::BitStringRef::as_bytes", "size": 18}, {"crate": "der", "name": "der::asn1::integer::uint::::value_len", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u64", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u8", "size": 18}, {"crate": "defmt", "name": "defmt::export::istr", "size": 18}, {"crate": "benchmark_code_size_no_tuf", "name": "benchmark_code_size_no_tuf::__cortex_m_rt_main", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}},cyw43::bus::Bus>::init::{{closure}}::{{closure}}>>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::wait_for_event::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::cmd_read::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::write::{{closure}}>", "size": 18}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset", "size": 16}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::buffer_len", "size": 16}, {"crate": "smoltcp", "name": ">::add_assign", "size": 16}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 16}, {"crate": "pem_rfc7468", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "p256", "name": "ff::Field::is_zero", "size": 16}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::pop_index", "size": 16}, {"crate": "embassy_executor", "name": "defmt::export::fmt", "size": 16}, {"crate": "der", "name": "der::tag::Tag::is_constructed", "size": 16}, {"crate": "der", "name": ">::spec_eq", "size": 16}, {"crate": "defmt", "name": "defmt::encoding::inner::Encoder::write::{{closure}}", "size": 16}, {"crate": "defmt", "name": "defmt::export::integers::u32", "size": 16}, {"crate": "defmt", "name": "::write_str", "size": 16}, {"crate": "defmt", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "std", "name": "::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "bt4ot_embedded", "name": "sec1::point::EncodedPoint::is_identity", "size": 16}, {"crate": "subtle?", "name": "::not", "size": 16}, {"crate": "embassy_net?", "name": " as smoltcp::phy::Device>::capabilities", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 16}, {"crate": "std", "name": "core::fmt::Formatter::write_fmt", "size": 14}, {"crate": "pkcs8", "name": "der::encode::EncodeValue::header", "size": 14}, {"crate": "p256", "name": "p256::arithmetic::scalar::Scalar::is_even", "size": 14}, {"crate": "p256", "name": " as crypto_bigint::traits::Integer>::is_odd", "size": 14}, {"crate": "embassy_time", "name": "_defmt_timestamp", "size": 14}, {"crate": "der", "name": "der::tag::Tag::number", "size": 14}, {"crate": "defmt", "name": "defmt::export::header", "size": 14}, {"crate": "crypto_bigint", "name": "crypto_bigint::ct_choice:: for subtle::Choice>::from", "size": 14}, {"crate": "std", "name": "core::str::slice_error_fail", "size": 14}, {"crate": "sha2?", "name": "::finalize_variable_core::{{closure}}", "size": 14}, {"name": "__Thumbv6MABSLongThunk__ZN10embassy_rp5flash11ram_helpers17write_flash_inner17h82c1bb1a60c73aa7E", "size": 12}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memclr8", "size": 12}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memclr", "size": 12}, {"crate": "x509_cert", "name": " as der::encode::EncodeValue>::value_len", "size": 12}, {"crate": "spki", "name": " as generic_array::sequence::GenericSequence>::generate", "size": 12}, {"crate": "spki", "name": " as generic_array::sequence::GenericSequence>::generate", "size": 12}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::push_index", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_0", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_1", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_2", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_3", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::clone", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::SyncExecutor::alarm_callback", "size": 12}, {"crate": "der", "name": "::value_len", "size": 12}, {"crate": "crypto_bigint", "name": "subtle::black_box", "size": 12}, {"crate": "bt4ot_embedded", "name": "<&T as core::fmt::Debug>::fmt", "size": 12}, {"name": "__aeabi_memmove8", "size": 10}, {"name": "__aeabi_memcpy8", "size": 10}, {"name": "__aeabi_memclr4", "size": 10}, {"name": "__aeabi_memcpy4", "size": 10}, {"name": "__aeabi_memcpy", "size": 10}, {"name": "__aeabi_uidiv", "size": 10}, {"name": "__aeabi_memclr", "size": 10}, {"name": "memcmp", "size": 10}, {"name": "__aeabi_memset4", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy", "size": 10}, {"name": "__aeabi_memclr8", "size": 10}, {"crate": "pkcs8", "name": "::encoded_len", "size": 10}, {"crate": "der", "name": "der::tag:: for u8>::from", "size": 10}, {"crate": "defmt", "name": "defmt::export::bool", "size": 10}, {"crate": "std", "name": "core::cmp::impls:: for &A>::eq", "size": 10}, {"crate": "x509_cert", "name": "x509_cert::time::Time::to_unix_duration", "size": 8}, {"crate": "defmt", "name": "defmt::default_panic::panic_cold_explicit", "size": 8}, {"crate": "defmt", "name": "__defmt_default_panic", "size": 8}, {"name": "main", "size": 8}, {"crate": "cortex_m_rt", "name": "HardFault_", "size": 2}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::drop", "size": 2}, {"crate": "defmt", "name": "__defmt_default_timestamp", "size": 2}, {"crate": "cortex_m_rt", "name": "DefaultHandler_", "size": 2}]}, "cargo_bloat_crates": {"file-size": 1773272, "text-section-size": 168980, "crates": [{"name": "bt4ot_embedded", "size": 28638}, {"name": "benchmark_code_size_no_tuf", "size": 27882}, {"name": "smoltcp", "size": 25114}, {"name": "std", "size": 15042}, {"name": "p256", "size": 12560}, {"name": "cyw43", "size": 10212}, {"name": "embassy_executor", "size": 8938}, {"name": "der", "size": 6866}, {"name": "pem_rfc7468", "size": 5458}, {"name": "sha2", "size": 3922}, {"name": "pkcs8", "size": 3838}, {"name": "x509_cert", "size": 3236}, {"name": "embassy_net", "size": 3182}, {"name": "spki", "size": 1336}, {"name": "embassy_rp", "size": 1336}, {"name": "tuf_no_std_der", "size": 1274}, {"name": "embassy_embedded_hal", "size": 1232}, {"name": "const_oid", "size": 1198}, {"name": "serde_json_core", "size": 1012}, {"name": "cyw43_pio", "size": 760}, {"name": "defmt", "size": 758}, {"name": "embassy_sync", "size": 622}, {"name": "defmt_rtt", "size": 466}, {"name": "tuf_no_std_common", "size": 432}, {"name": "[Unknown]", "size": 412}, {"name": "ecdsa", "size": 346}, {"name": "sec1", "size": 304}, {"name": "panic_probe", "size": 276}, {"name": "pio", "size": 244}, {"name": "base64ct", "size": 228}, {"name": "crypto_bigint", "size": 228}, {"name": "embassy_boot", "size": 198}, {"name": "embassy_time", "size": 134}, {"name": "embedded_io_async", "size": 132}, {"name": "heapless", "size": 130}, {"name": "byteorder", "size": 120}, {"name": "generic_array", "size": 88}, {"name": "serde", "size": 88}, {"name": "bt4ot_ota_common", "size": 84}, {"name": "hex", "size": 72}, {"name": "embassy_net_driver_channel", "size": 60}, {"name": "subtle", "size": 56}, {"name": "embassy_hal_internal", "size": 48}, {"name": "cortex_m_rt", "size": 44}, {"name": "cortex_m", "size": 36}, {"name": "embassy_net_driver", "size": 36}, {"name": "portable_atomic", "size": 32}, {"name": "no_std_net", "size": 30}, {"name": "atomic_polyfill", "size": 28}, {"name": "critical_section", "size": 28}, {"name": "digest", "size": 26}, {"name": "hash32", "size": 20}]}}, "benchmark-code-size-signature-only": {"raw_size": 147548, "cargo_bloat": {"file-size": 1324176, "text-section-size": 121812, "functions": [{"crate": "benchmark_code_size_signature_only", "name": "benchmark_code_size_signature_only::____embassy_main_task::{{closure}}", "size": 25908}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ethernet::::process_ethernet", "size": 9676}, {"crate": "p256", "name": "::mul", "size": 5526}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 5452}, {"crate": "cyw43", "name": "cyw43::runner::Runner::check_status::{{closure}}", "size": 4320}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::dispatch_ip", "size": 4264}, {"crate": "sha2", "name": "sha2::sha256::compress256", "size": 3440}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 2528}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_mul", "size": 2422}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::montgomery_reduce", "size": 1670}, {"crate": "std", "name": "core::fmt::Formatter::pad", "size": 1536}, {"crate": "std", "name": "core::char::methods::::escape_debug_ext", "size": 1020}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u64_div_rem", "size": 938}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::write", "size": 828}, {"crate": "std", "name": "compiler_builtins::mem::memmove", "size": 788}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::parse_ack", "size": 728}, {"crate": "std", "name": "core::str::slice_error_fail_rt", "size": 668}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::poll_at::{{closure}}", "size": 650}, {"crate": "der?", "name": "::fmt", "size": 612}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 608}, {"crate": "std", "name": "::write_str", "size": 604}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::write::{{closure}}", "size": 584}, {"crate": "tuf_no_std_common", "name": ">::add", "size": 560}, {"crate": "std", "name": "core::str::converts::from_utf8", "size": 552}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::connect::{{closure}}", "size": 552}, {"crate": "der", "name": "::decode", "size": 548}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Repr::emit", "size": 544}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::read::{{closure}}", "size": 532}, {"crate": "tuf_no_std_common", "name": " as core::ops::arith::Mul>::mul", "size": 500}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Repr::emit", "size": 500}, {"crate": "p256", "name": "p256::arithmetic::scalar::scalar_impl::subtract_n_if_necessary", "size": 488}, {"crate": "tuf_no_std_der", "name": " as elliptic_curve::point::DecompressPoint>::decompress", "size": 480}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral", "size": 478}, {"crate": "der", "name": "<&T as core::fmt::Display>::fmt", "size": 472}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::sub_inner", "size": 464}, {"crate": "pkcs8", "name": "::decode", "size": 456}, {"crate": "pkcs8", "name": "::decode", "size": 456}, {"crate": "tuf_no_std_common", "name": ">::double", "size": 448}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 440}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 440}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_reset::{{closure}}", "size": 428}, {"crate": "embassy_net", "name": "embassy_net::Inner::apply_static_config", "size": 428}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::TcpOption::emit", "size": 412}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}", "size": 408}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_disable::{{closure}}", "size": 400}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::run", "size": 400}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar::{{closure}}", "size": 396}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u32_div_rem", "size": 392}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write::{{closure}}", "size": 384}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read::{{closure}}", "size": 376}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::new", "size": 360}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}", "size": 352}, {"crate": "cyw43", "name": "cyw43::runner::Runner::handle_irq::{{closure}}", "size": 344}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_add", "size": 340}, {"crate": "std", "name": "compiler_builtins::mem::memcpy", "size": 334}, {"crate": "ecdsa", "name": "::decode", "size": 332}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 332}, {"crate": "const_oid", "name": "const_oid::arcs::Arcs::try_next", "size": 332}, {"crate": "std", "name": "core::fmt::write", "size": 330}, {"crate": "der", "name": ">::try_from", "size": 324}, {"crate": "std", "name": "core::fmt::builders::DebugStruct::field", "size": 324}, {"crate": "cyw43", "name": "cyw43::control::Control::get_iovar::{{closure}}", "size": 292}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_set_window::{{closure}}", "size": 288}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_reply", "size": 280}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl::{{closure}}", "size": 272}, {"crate": "std", "name": "core::unicode::printable::check", "size": 260}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 256}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 248}, {"crate": "pio", "name": "pio::InstructionOperands::encode", "size": 244}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 244}, {"crate": "sha2", "name": "sha2::sha256::soft::schedule", "size": 238}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 236}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wlan_write::{{closure}}", "size": 232}, {"crate": "sha2", "name": "sha2::sha256::soft::sha256_digest_round_x2", "size": 230}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wait_for_event::{{closure}}", "size": 224}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reset", "size": 222}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_broadcast_v4", "size": 220}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup", "size": 220}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::erase", "size": 216}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::remove_default_ipv4_route", "size": 204}, {"crate": "const_oid?", "name": "::fmt", "size": 204}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32x2::{{closure}}", "size": 204}, {"crate": "smoltcp", "name": "heapless::linear_map::LinearMap::insert", "size": 196}, {"crate": "std", "name": "core::fmt::Formatter::debug_tuple_field1_finish", "size": 196}, {"crate": "cyw43", "name": "cyw43::bus::Bus::readn::{{closure}}", "size": 196}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::flush::{{closure}}", "size": 196}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::rst_reply", "size": 190}, {"crate": "p256", "name": "::add_assign", "size": 188}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ipv4::::icmpv4_reply", "size": 186}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Repr::parse", "size": 184}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_writen::{{closure}}", "size": 184}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::seq_to_transmit", "size": 180}, {"crate": "embassy_rp", "name": "embassy_rp::pio::Common::try_load_program_at", "size": 176}, {"crate": "std", "name": "core::cmp::PartialEq::ne", "size": 172}, {"crate": "const_oid", "name": "<&T as core::fmt::Display>::fmt", "size": 168}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_readn::{{closure}}", "size": 168}, {"crate": "der", "name": "::read_slice", "size": 166}, {"crate": "std", "name": "compiler_builtins::mem::memset", "size": 160}, {"crate": "std", "name": "core::panicking::assert_failed_inner", "size": 160}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::add::put", "size": 158}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_many", "size": 156}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::data", "size": 152}, {"crate": "panic_probe", "name": "<&T as core::fmt::Display>::fmt", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 152}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::iface::neighbor::Cache::lookup", "size": 148}, {"crate": "embassy_net", "name": "::drop", "size": 148}, {"crate": "cyw43", "name": "cyw43::bus::Bus::writen::{{closure}}", "size": 148}, {"crate": "tuf_no_std_common", "name": "<&T as core::fmt::Debug>::fmt", "size": 144}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::next_header", "size": 144}, {"crate": "const_oid", "name": "const_oid::ObjectIdentifier::from_bytes", "size": 144}, {"crate": "defmt_rtt", "name": "_defmt_write", "size": 140}, {"crate": "spki?", "name": "::fmt", "size": 140}, {"crate": "sec1?", "name": "::fmt", "size": 136}, {"crate": "p256", "name": ">::sub_assign", "size": 136}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write32_swapped::{{closure}}", "size": 136}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm", "size": 132}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32_swapped::{{closure}}", "size": 132}, {"crate": "embedded_io_async", "name": "embedded_io_async::Read::read_exact::{{closure}}", "size": 132}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_function_pointers_with_boot2", "size": 128}, {"crate": "der", "name": "der::tag::Tag::assert_eq", "size": 128}, {"crate": "std", "name": "core::fmt::Formatter::debug_struct_field2_finish", "size": 128}, {"crate": "defmt", "name": "core::fmt::Write::write_char", "size": 126}, {"crate": "panic_probe", "name": "rust_begin_unwind", "size": 124}, {"crate": "cyw43", "name": "cyw43::control::Control::gpio_set::{{closure}}", "size": 124}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::Repr::buffer_len", "size": 120}, {"crate": "embassy_net", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 120}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 120}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 120}, {"crate": "const_oid", "name": "<&T as core::fmt::Debug>::fmt", "size": 120}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::fill_checksum", "size": 116}, {"crate": "embassy_rp", "name": "IO_IRQ_BANK0", "size": 116}, {"crate": "std", "name": "core::fmt::Formatter::debug_struct_field1_finish", "size": 116}, {"crate": "tuf_no_std_common", "name": "<&T as core::fmt::Debug>::fmt", "size": 112}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe_sub", "size": 112}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy4", "size": 110}, {"crate": "embassy_executor", "name": "_embassy_time_schedule_wake", "size": 108}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl_set_u32::{{closure}}", "size": 108}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::remove", "size": 104}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_unallocated", "size": 104}, {"crate": "embassy_rp", "name": "embassy_rp::time_driver::TimerDriver::check_alarm", "size": 104}, {"crate": "embassy_boot?", "name": "::fmt", "size": 104}, {"crate": "std", "name": "::fmt", "size": 102}, {"crate": "embassy_rp", "name": "DMA_IRQ_0", "size": 100}, {"crate": "der", "name": "der::reader::Reader::read_into", "size": 100}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 100}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32::{{closure}}", "size": 100}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::advance_position", "size": 98}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset4", "size": 96}, {"crate": "smoltcp", "name": "::fmt", "size": 96}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::advance_position", "size": 96}, {"crate": "cyw43", "name": "cyw43::structs::BdcHeader::parse", "size": 96}, {"crate": "std", "name": "core::ptr::drop_in_place>>", "size": 96}, {"crate": "embassy_embedded_hal", "name": "embassy_embedded_hal::flash::partition::blocking::BlockingPartition::new", "size": 96}, {"crate": "embassy_boot", "name": "embassy_boot::firmware_updater::blocking::BlockingFirmwareState::get_state", "size": 94}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reply", "size": 92}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write16::{{closure}}", "size": 92}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::ReadNorFlash>::read", "size": 92}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::route", "size": 90}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_neighbor", "size": 90}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 88}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup::{{closure}}", "size": 88}, {"crate": "p256", "name": ">>::reduce_bytes", "size": 88}, {"crate": "generic_array", "name": "generic_array::GenericArray::clone_from_slice", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write32::{{closure}}", "size": 88}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::pseudo_header", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::challenge_ack_reply", "size": 84}, {"crate": "pkcs8", "name": "der::reader::nested::NestedReader::new", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::register", "size": 84}, {"crate": "embassy_net", "name": "defmt::export::fmt", "size": 84}, {"crate": "defmt_rtt", "name": "_defmt_acquire", "size": 84}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::write_impl", "size": 84}, {"crate": "std", "name": "core::ops::function::FnMut::call_mut", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write8::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read8::{{closure}}", "size": 84}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Receiver::receive_done", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::send_done", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::new_checked", "size": 80}, {"crate": "smoltcp", "name": "::next", "size": 80}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::to_canonical", "size": 80}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_range_program", "size": 80}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::DhcpOptionWriter::emit", "size": 76}, {"crate": "smoltcp", "name": "::fmt", "size": 76}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::header_len", "size": 76}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_into", "size": 76}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::array::>::from_be_byte_array", "size": 76}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "bt4ot_embedded", "name": "<&T as core::fmt::Debug>::fmt", "size": 76}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 76}, {"name": "__aeabi_lmul", "size": 74}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::to_bytes", "size": 74}, {"crate": "smoltcp", "name": "heapless::vec::Vec::clone", "size": 72}, {"crate": "der", "name": "der::length::Length::initial_octet", "size": 72}, {"crate": "embassy_rp", "name": "::acquire", "size": 72}, {"crate": "embassy_net", "name": "embassy_net::tcp::TcpSocket::set_timeout", "size": 72}, {"crate": "der", "name": "der::reader::slice::SliceReader::remaining", "size": 72}, {"crate": "defmt_rtt", "name": "_defmt_release", "size": 72}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::poll_send", "size": 72}, {"crate": "std", "name": "__clzsi2", "size": 70}, {"crate": "std", "name": ">::into", "size": 68}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::in_same_network", "size": 68}, {"crate": "p256", "name": "crypto_bigint::uint::add::>::wrapping_add", "size": 68}, {"crate": "std", "name": "core::result::unwrap_failed", "size": 68}, {"crate": "std", "name": "::write_char", "size": 68}, {"crate": "pkcs8", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "pkcs8", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "der", "name": "der::reader::Reader::read_byte", "size": 66}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 64}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::from_bytes", "size": 64}, {"crate": "p256", "name": ">::shr_assign", "size": 64}, {"crate": "p256", "name": " as subtle::ConditionallySelectable>::conditional_select", "size": 64}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 64}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 64}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::blocking_write", "size": 64}, {"crate": "const_oid", "name": "::next", "size": 64}, {"crate": "pkcs8", "name": "::decode_value", "size": 62}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::sqn", "size": 62}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral::write_prefix", "size": 62}, {"crate": "std", "name": " as core::fmt::Debug>::fmt", "size": 60}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_one", "size": 60}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 60}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 60}, {"crate": "std", "name": "core::slice::::copy_from_slice::len_mismatch_fail", "size": 60}, {"crate": "std", "name": " as core::fmt::Debug>::fmt", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_index_order_fail", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_end_index_len_fail", "size": 60}, {"crate": "std", "name": "core::panicking::panic_bounds_check", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_start_index_len_fail", "size": 60}, {"crate": "std", "name": "core::ptr::drop_in_place>::init::{{closure}}>", "size": 60}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::wake", "size": 58}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 58}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_ip_addr", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "pkcs8", "name": " as der::reader::Reader>::read_slice", "size": 56}, {"crate": "embassy_executor", "name": "__pender", "size": 56}, {"crate": "std", "name": "core::panicking::panic_explicit", "size": 56}, {"crate": "bt4ot_embedded", "name": "elliptic_curve::scalar::primitive::ScalarPrimitive::from_slice", "size": 56}, {"name": "PIO0_IRQ_0", "size": 56}, {"crate": "heapless", "name": "heapless::deque::Deque::pop_front", "size": 56}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 56}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 56}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::try_send", "size": 56}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::register", "size": 54}, {"crate": "tuf_no_std_common", "name": "sec1::point::EncodedPoint::tag", "size": 52}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet<&T>::payload", "size": 52}, {"crate": "embassy_time", "name": "::poll", "size": 52}, {"crate": "embassy_rp", "name": "__pre_init", "size": 52}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm_callback", "size": 52}, {"crate": "embassy_net", "name": "embassy_net::to_smoltcp_hardware_address", "size": 52}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::new", "size": 52}, {"crate": "der", "name": "::fmt", "size": 52}, {"crate": "std", "name": "core::slice::::split_at_mut", "size": 52}, {"crate": "std", "name": "core::option::expect_failed", "size": 52}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_reset::{{closure}}>", "size": 52}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::register", "size": 50}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Address::from_bytes", "size": 48}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 48}, {"crate": "smoltcp", "name": "defmt::traits::Format::_format_data", "size": 48}, {"crate": "smoltcp", "name": "::sub", "size": 48}, {"crate": "smoltcp", "name": ">::add", "size": 48}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::Uint<_>::from_words", "size": 48}, {"crate": "embassy_hal_internal", "name": "embassy_hal_internal::interrupt::InterruptExt::set_priority", "size": 48}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_mutably_borrowed", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_borrowed", "size": 48}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 48}, {"crate": "bt4ot_embedded", "name": "ecdsa::der::Signature::s", "size": 48}, {"crate": "bt4ot_embedded", "name": "ecdsa::der::Signature::r", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_src_addr", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_dst_addr", "size": 48}, {"crate": "tuf_no_std_common?", "name": "::fmt", "size": 48}, {"crate": "embassy_sync?", "name": "::fmt", "size": 48}, {"crate": "std", "name": "compiler_builtins::mem::memcmp", "size": 46}, {"name": "__aeabi_llsl", "size": 44}, {"crate": "byteorder", "name": "::write_u32", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::msg_type", "size": 44}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 44}, {"crate": "defmt", "name": "defmt::impls::primitives::::_format_data", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_to_transmit", "size": 44}, {"crate": "embassy_rp", "name": "_embassy_time_allocate_alarm", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_ethertype", "size": 44}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::new_checked", "size": 42}, {"crate": "heapless?", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_disable::{{closure}}>", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::handle_irq::{{closure}}>", "size": 42}, {"crate": "subtle", "name": "subtle::CtOption::unwrap", "size": 40}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&mut T>::data_mut", "size": 40}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::cmp::>::ct_lt", "size": 40}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 40}, {"crate": "der", "name": "der::asn1::integer::uint::strip_leading_zeroes", "size": 40}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_rem_by_zero", "size": 40}, {"crate": "const_oid", "name": ">::try_from", "size": 40}, {"crate": "byteorder", "name": "::write_u16", "size": 40}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 40}, {"crate": "cortex_m_rt", "name": "Reset", "size": 40}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Timer::should_keep_alive", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::contiguous_window", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_unicast_v4", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::window_to_update", "size": 38}, {"crate": "p256", "name": "p256::arithmetic::field::field_impl::fe32_to_fe64", "size": 38}, {"crate": "crypto_bigint", "name": "crypto_bigint::uint::cmp::>::ct_eq", "size": 38}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::wake", "size": 38}, {"crate": "const_oid", "name": "<&T as core::fmt::Debug>::fmt", "size": 38}, {"crate": "embassy_net?", "name": "::clone", "size": 38}, {"crate": "p256?", "name": "::neg", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_ident", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_type", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::payload_mut", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_ident", "size": 36}, {"crate": "byteorder", "name": "::read_u16", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::reset", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::total_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::scaled_window", "size": 36}, {"crate": "embassy_time", "name": ">::add", "size": 36}, {"crate": "embassy_rp", "name": "::release", "size": 36}, {"crate": "embassy_net_driver", "name": "defmt::export::fmt_array", "size": 36}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 36}, {"crate": "der", "name": "<&T as core::fmt::Debug>::fmt", "size": 36}, {"crate": "std", "name": "::fmt", "size": 36}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_async_fn_resumed", "size": 36}, {"crate": "std", "name": "core::panicking::panic", "size": 36}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 36}, {"crate": "der?", "name": "::fmt", "size": 36}, {"crate": "embassy_net_driver_channel?", "name": " as embassy_net_driver::Driver>::hardware_address", "size": 36}, {"crate": "cortex_m", "name": "cortex_m::peripheral::scb::::sys_reset", "size": 36}, {"crate": "embassy_rp?", "name": " as core::future::future::Future>::poll", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 36}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 36}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::set_pindir", "size": 36}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 36}, {"crate": "const_oid?", "name": "::eq", "size": 34}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::set_state", "size": 34}, {"crate": "ecdsa", "name": "ecdsa::der::find_scalar_range", "size": 34}, {"crate": "bt4ot_embedded", "name": "crypto_bigint::traits::Zero::is_zero", "size": 34}, {"crate": "std", "name": "core::iter::traits::iterator::Iterator::nth", "size": 34}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 34}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Address::from_bytes", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::dst_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::src_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&T>::data", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::dst_addr", "size": 32}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 32}, {"crate": "const_oid", "name": "const_oid::ObjectIdentifier::as_bytes", "size": 32}, {"crate": "heapless", "name": "heapless::deque::Deque::len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_writen::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read::{{closure}}>", "size": 32}, {"crate": "portable_atomic", "name": "portable_atomic::AtomicBool::compare_exchange", "size": 32}, {"crate": "embassy_time", "name": "embassy_time::timer::Timer::after_millis", "size": 32}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::multi_waker::MultiWakerRegistration<_>::wake", "size": 32}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::exec_jmp", "size": 32}, {"crate": "tuf_no_std_der", "name": "elliptic_curve::scalar::nonzero::NonZeroScalar::new", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::combine", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Cidr::contains_addr", "size": 30}, {"crate": "p256", "name": "::double", "size": 30}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::square", "size": 30}, {"crate": "no_std_net", "name": "core::slice::::copy_from_slice", "size": 30}, {"crate": "std", "name": "core::panicking::panic_fmt", "size": 30}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_readn::{{closure}}>", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::log_init::{{closure}}>", "size": 30}, {"crate": "p256?", "name": ">::add", "size": 28}, {"crate": "p256?", "name": ">::mul", "size": 28}, {"crate": "p256?", "name": "::sub", "size": 28}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::advance", "size": 28}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 28}, {"crate": "p256", "name": "p256::arithmetic::field::FieldElement::multiply", "size": 28}, {"crate": "critical_section", "name": "critical_section::with", "size": 28}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 28}, {"crate": "atomic_polyfill", "name": "atomic_polyfill::polyfill::AtomicU32::fetch_or", "size": 28}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 28}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::wait_for_event::{{closure}}>", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place::{{closure}}>", "size": 28}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_idx_unchecked", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::accept_digit", "size": 26}, {"crate": "std", "name": "core::result::Result::and_then", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "digest", "name": " as digest::Update>::update::{{closure}}", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_code", "size": 24}, {"crate": "smoltcp?", "name": "::fmt", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::hardware_len", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::hop_limit", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame<&T>::payload", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "signature", "name": "::fmt", "size": 24}, {"crate": "p256", "name": "core::panicking::assert_failed", "size": 24}, {"crate": "embassy_rp", "name": "embassy_rp::reset::unreset_wait", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::Executor::spawn", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 24}, {"crate": "defmt", "name": "defmt::export::str", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "defmt", "name": "core::fmt::Write::write_fmt", "size": 24}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::payload_mut", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::protocol_len", "size": 24}, {"crate": "embassy_net_driver_channel?", "name": "::fmt", "size": 24}, {"crate": "embassy_executor?", "name": "::fmt", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 24}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 24}, {"name": "__aeabi_uldivmod", "size": 22}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::wake", "size": 22}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::wake", "size": 22}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::nonblocking_write", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 22}, {"name": "HardFaultTrampoline", "size": 20}, {"crate": "hash32", "name": "<&T as core::fmt::Debug>::fmt", "size": 20}, {"crate": "embassy_rp", "name": "::now", "size": 20}, {"crate": "std", "name": "core::option::unwrap_failed", "size": 20}, {"crate": "std", "name": "core::fmt::Write::write_fmt", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_set_window::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::check_status::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_is_up::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write32::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read8::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write16::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::writen::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::readn::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read8::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "<() as core::fmt::Debug>::fmt", "size": 20}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 20}, {"crate": "std", "name": "__udivmodsi4", "size": 18}, {"name": "__aeabi_uidivmod", "size": 18}, {"crate": "std", "name": "__udivmoddi4", "size": 18}, {"crate": "tuf_no_std_der", "name": "sec1::point::EncodedPoint::is_identity", "size": 18}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::config_changed", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u64", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u8", "size": 18}, {"crate": "defmt", "name": "defmt::export::istr", "size": 18}, {"crate": "benchmark_code_size_signature_only", "name": "benchmark_code_size_signature_only::__cortex_m_rt_main", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}},cyw43::bus::Bus>::init::{{closure}}::{{closure}}>>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::wait_for_event::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::cmd_read::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::write::{{closure}}>", "size": 18}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset", "size": 16}, {"crate": "subtle?", "name": "::not", "size": 16}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::buffer_len", "size": 16}, {"crate": "smoltcp", "name": ">::add_assign", "size": 16}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 16}, {"crate": "pem_rfc7468", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "p256", "name": "ff::Field::is_zero", "size": 16}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::pop_index", "size": 16}, {"crate": "embassy_executor", "name": "defmt::export::fmt", "size": 16}, {"crate": "defmt", "name": "defmt::encoding::inner::Encoder::write::{{closure}}", "size": 16}, {"crate": "defmt", "name": "defmt::export::integers::u32", "size": 16}, {"crate": "defmt", "name": "::write_str", "size": 16}, {"crate": "defmt", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "std", "name": "::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "embassy_net?", "name": " as smoltcp::phy::Device>::capabilities", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 16}, {"crate": "sha2?", "name": "::finalize_variable_core::{{closure}}", "size": 14}, {"crate": "std", "name": "core::fmt::Formatter::write_fmt", "size": 14}, {"crate": "p256", "name": "p256::arithmetic::scalar::Scalar::is_even", "size": 14}, {"crate": "p256", "name": " as crypto_bigint::traits::Integer>::is_odd", "size": 14}, {"crate": "embassy_time", "name": "_defmt_timestamp", "size": 14}, {"crate": "defmt", "name": "defmt::export::header", "size": 14}, {"crate": "crypto_bigint", "name": "crypto_bigint::ct_choice:: for subtle::Choice>::from", "size": 14}, {"crate": "std", "name": "core::str::slice_error_fail", "size": 14}, {"name": "__Thumbv6MABSLongThunk__ZN10embassy_rp5flash11ram_helpers17write_flash_inner17h82c1bb1a60c73aa7E", "size": 12}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memclr8", "size": 12}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memclr", "size": 12}, {"crate": "spki", "name": " as generic_array::sequence::GenericSequence>::generate", "size": 12}, {"crate": "spki", "name": " as generic_array::sequence::GenericSequence>::generate", "size": 12}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::push_index", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_0", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_1", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_2", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_3", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::clone", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::SyncExecutor::alarm_callback", "size": 12}, {"crate": "crypto_bigint", "name": "subtle::black_box", "size": 12}, {"name": "__aeabi_memmove8", "size": 10}, {"name": "__aeabi_memcpy8", "size": 10}, {"name": "__aeabi_memclr4", "size": 10}, {"name": "__aeabi_memcpy4", "size": 10}, {"name": "__aeabi_memcpy", "size": 10}, {"name": "__aeabi_uidiv", "size": 10}, {"name": "__aeabi_memclr", "size": 10}, {"name": "memcmp", "size": 10}, {"name": "__aeabi_memset4", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy", "size": 10}, {"name": "__aeabi_memclr8", "size": 10}, {"crate": "defmt", "name": "defmt::export::bool", "size": 10}, {"crate": "defmt", "name": "defmt::default_panic::panic_cold_explicit", "size": 8}, {"crate": "defmt", "name": "__defmt_default_panic", "size": 8}, {"name": "main", "size": 8}, {"crate": "cortex_m_rt", "name": "HardFault_", "size": 2}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::drop", "size": 2}, {"crate": "defmt", "name": "__defmt_default_timestamp", "size": 2}, {"crate": "cortex_m_rt", "name": "DefaultHandler_", "size": 2}]}, "cargo_bloat_crates": {"file-size": 1324176, "text-section-size": 121812, "crates": [{"name": "benchmark_code_size_signature_only", "size": 25926}, {"name": "smoltcp", "size": 25114}, {"name": "std", "size": 14228}, {"name": "p256", "size": 12224}, {"name": "cyw43", "size": 10212}, {"name": "embassy_executor", "size": 8938}, {"name": "der", "size": 4212}, {"name": "sha2", "size": 3922}, {"name": "embassy_net", "size": 3182}, {"name": "tuf_no_std_common", "size": 1864}, {"name": "pkcs8", "size": 1592}, {"name": "embassy_rp", "size": 1336}, {"name": "embassy_embedded_hal", "size": 1232}, {"name": "const_oid", "size": 1176}, {"name": "cyw43_pio", "size": 760}, {"name": "defmt", "size": 758}, {"name": "embassy_sync", "size": 622}, {"name": "tuf_no_std_der", "size": 528}, {"name": "defmt_rtt", "size": 466}, {"name": "ecdsa", "size": 366}, {"name": "[Unknown]", "size": 354}, {"name": "panic_probe", "size": 276}, {"name": "bt4ot_embedded", "size": 262}, {"name": "pio", "size": 244}, {"name": "crypto_bigint", "size": 228}, {"name": "embassy_boot", "size": 198}, {"name": "spki", "size": 164}, {"name": "sec1", "size": 136}, {"name": "embassy_time", "size": 134}, {"name": "embedded_io_async", "size": 132}, {"name": "heapless", "size": 130}, {"name": "byteorder", "size": 120}, {"name": "generic_array", "size": 88}, {"name": "bt4ot_ota_common", "size": 84}, {"name": "embassy_net_driver_channel", "size": 60}, {"name": "subtle", "size": 56}, {"name": "embassy_hal_internal", "size": 48}, {"name": "cortex_m_rt", "size": 44}, {"name": "cortex_m", "size": 36}, {"name": "embassy_net_driver", "size": 36}, {"name": "portable_atomic", "size": 32}, {"name": "no_std_net", "size": 30}, {"name": "critical_section", "size": 28}, {"name": "atomic_polyfill", "size": 28}, {"name": "digest", "size": 26}, {"name": "signature", "size": 24}, {"name": "hash32", "size": 20}, {"name": "pem_rfc7468", "size": 16}]}}, "benchmark-code-size-update-only": {"raw_size": 103556, "cargo_bloat": {"file-size": 1031252, "text-section-size": 84740, "functions": [{"crate": "benchmark_code_size_update_only", "name": "benchmark_code_size_update_only::____embassy_main_task::{{closure}}", "size": 17416}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ethernet::::process_ethernet", "size": 9676}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 5452}, {"crate": "cyw43", "name": "cyw43::runner::Runner::check_status::{{closure}}", "size": 4348}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::dispatch_ip", "size": 4260}, {"crate": "digest", "name": " as digest::Update>::update::{{closure}}", "size": 3460}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 2528}, {"crate": "std", "name": "core::fmt::Formatter::pad", "size": 1536}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u64_div_rem", "size": 938}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::write", "size": 828}, {"crate": "std", "name": "compiler_builtins::mem::memmove", "size": 788}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::parse_ack", "size": 728}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::poll_at::{{closure}}", "size": 650}, {"crate": "std", "name": "::write_str", "size": 604}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::write::{{closure}}", "size": 584}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Repr::emit", "size": 544}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::read::{{closure}}", "size": 532}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Repr::emit", "size": 500}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral", "size": 478}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 440}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_reset::{{closure}}", "size": 428}, {"crate": "embassy_net", "name": "embassy_net::Inner::apply_static_config", "size": 428}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::TcpOption::emit", "size": 412}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}", "size": 408}, {"crate": "cyw43", "name": "cyw43::runner::Runner::core_disable::{{closure}}", "size": 400}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::run", "size": 400}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar::{{closure}}", "size": 396}, {"crate": "std", "name": "compiler_builtins::int::specialized_div_rem::u32_div_rem", "size": 392}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write::{{closure}}", "size": 384}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read::{{closure}}", "size": 376}, {"crate": "cyw43_pio?", "name": " as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}", "size": 352}, {"crate": "cyw43", "name": "cyw43::runner::Runner::handle_irq::{{closure}}", "size": 344}, {"crate": "std", "name": "compiler_builtins::mem::memcpy", "size": 334}, {"crate": "std", "name": "core::fmt::write", "size": 330}, {"crate": "cyw43", "name": "cyw43::control::Control::get_iovar::{{closure}}", "size": 292}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_set_window::{{closure}}", "size": 288}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_reply", "size": 280}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl::{{closure}}", "size": 272}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 256}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 248}, {"crate": "pio", "name": "pio::InstructionOperands::encode", "size": 244}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 244}, {"crate": "sha2", "name": "sha2::sha256::soft::schedule", "size": 238}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wlan_write::{{closure}}", "size": 232}, {"crate": "sha2", "name": "sha2::sha256::soft::sha256_digest_round_x2", "size": 230}, {"crate": "cyw43", "name": "cyw43::bus::Bus::wait_for_event::{{closure}}", "size": 224}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reset", "size": 222}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_broadcast_v4", "size": 220}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup", "size": 220}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::NorFlash>::erase", "size": 216}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::remove_default_ipv4_route", "size": 204}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32x2::{{closure}}", "size": 204}, {"crate": "smoltcp", "name": "heapless::linear_map::LinearMap::insert", "size": 196}, {"crate": "std", "name": "core::fmt::Formatter::debug_tuple_field1_finish", "size": 196}, {"crate": "cyw43", "name": "cyw43::bus::Bus::readn::{{closure}}", "size": 196}, {"crate": "embassy_net", "name": "embassy_net::tcp::embedded_io_impls::::flush::{{closure}}", "size": 196}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::rst_reply", "size": 190}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::ipv4::::icmpv4_reply", "size": 186}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Repr::parse", "size": 184}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_writen::{{closure}}", "size": 184}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::seq_to_transmit", "size": 180}, {"crate": "embassy_rp", "name": "embassy_rp::pio::Common::try_load_program_at", "size": 176}, {"crate": "std", "name": "core::cmp::PartialEq::ne", "size": 172}, {"crate": "cyw43", "name": "cyw43::bus::Bus::backplane_readn::{{closure}}", "size": 168}, {"crate": "std", "name": "compiler_builtins::mem::memset", "size": 160}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::add::put", "size": 158}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_many", "size": 156}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::data", "size": 152}, {"crate": "panic_probe", "name": "<&T as core::fmt::Display>::fmt", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 152}, {"crate": "smoltcp", "name": "smoltcp::iface::neighbor::Cache::lookup", "size": 148}, {"crate": "cyw43", "name": "cyw43::bus::Bus::writen::{{closure}}", "size": 148}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::next_header", "size": 144}, {"crate": "defmt_rtt", "name": "_defmt_write", "size": 140}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write32_swapped::{{closure}}", "size": 136}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm", "size": 132}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32_swapped::{{closure}}", "size": 132}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_function_pointers_with_boot2", "size": 128}, {"crate": "defmt", "name": "core::fmt::Write::write_char", "size": 126}, {"crate": "panic_probe", "name": "rust_begin_unwind", "size": 124}, {"crate": "cyw43", "name": "cyw43::control::Control::gpio_set::{{closure}}", "size": 124}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::Repr::buffer_len", "size": 120}, {"crate": "embassy_net", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "std", "name": "core::fmt::num::imp::::fmt", "size": 120}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::get_mut", "size": 120}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::fill_checksum", "size": 116}, {"crate": "embassy_rp", "name": "IO_IRQ_BANK0", "size": 116}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy4", "size": 110}, {"crate": "embassy_executor", "name": "_embassy_time_schedule_wake", "size": 108}, {"crate": "cyw43", "name": "cyw43::control::Control::ioctl_set_u32::{{closure}}", "size": 108}, {"crate": "smoltcp", "name": "smoltcp::iface::socket_set::SocketSet::remove", "size": 104}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_unallocated", "size": 104}, {"crate": "embassy_rp", "name": "embassy_rp::time_driver::TimerDriver::check_alarm", "size": 104}, {"crate": "embassy_boot?", "name": "::fmt", "size": 104}, {"crate": "embassy_rp", "name": "DMA_IRQ_0", "size": 100}, {"crate": "std", "name": "core::fmt::num::::fmt", "size": 100}, {"crate": "cyw43", "name": "cyw43::control::Control::set_iovar_u32::{{closure}}", "size": 100}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset4", "size": 96}, {"crate": "smoltcp", "name": "::fmt", "size": 96}, {"crate": "std", "name": "core::ptr::drop_in_place>>", "size": 96}, {"crate": "embassy_embedded_hal", "name": "embassy_embedded_hal::flash::partition::blocking::BlockingPartition::new", "size": 96}, {"crate": "embassy_boot", "name": "embassy_boot::firmware_updater::blocking::BlockingFirmwareState::get_state", "size": 94}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::reply", "size": 92}, {"crate": "cyw43", "name": "cyw43::structs::BdcHeader::parse", "size": 92}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write16::{{closure}}", "size": 92}, {"crate": "embassy_embedded_hal?", "name": " as embedded_storage::nor_flash::ReadNorFlash>::read", "size": 92}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::route", "size": 90}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_neighbor", "size": 90}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 88}, {"crate": "smoltcp", "name": "smoltcp::iface::route::Routes::lookup::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_read8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::write8::{{closure}}", "size": 88}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write32::{{closure}}", "size": 88}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::pseudo_header", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::challenge_ack_reply", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::register", "size": 84}, {"crate": "embassy_net", "name": "defmt::export::fmt", "size": 84}, {"crate": "defmt_rtt", "name": "_defmt_acquire", "size": 84}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::write_impl", "size": 84}, {"crate": "std", "name": "core::ops::function::FnMut::call_mut", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::bp_write8::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read32::{{closure}}", "size": 84}, {"crate": "cyw43", "name": "cyw43::bus::Bus::read8::{{closure}}", "size": 84}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Receiver::receive_done", "size": 84}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::send_done", "size": 84}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::new_checked", "size": 80}, {"crate": "smoltcp", "name": "::next", "size": 80}, {"crate": "embassy_rp", "name": "embassy_rp::flash::ram_helpers::flash_range_program", "size": 80}, {"crate": "smoltcp", "name": "smoltcp::wire::dhcpv4::DhcpOptionWriter::emit", "size": 76}, {"crate": "smoltcp", "name": "::fmt", "size": 76}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::header_len", "size": 76}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 76}, {"name": "__aeabi_lmul", "size": 74}, {"crate": "smoltcp", "name": "heapless::vec::Vec::clone", "size": 72}, {"crate": "embassy_rp", "name": "::acquire", "size": 72}, {"crate": "defmt_rtt", "name": "_defmt_release", "size": 72}, {"crate": "std", "name": "core::result::unwrap_failed", "size": 72}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::poll_send", "size": 72}, {"crate": "std", "name": "__clzsi2", "size": 70}, {"crate": "std", "name": ">::into", "size": 68}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::in_same_network", "size": 68}, {"crate": "std", "name": "::write_char", "size": 68}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 64}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::blocking_write", "size": 64}, {"crate": "std", "name": "core::fmt::Formatter::pad_integral::write_prefix", "size": 62}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::enqueue_one", "size": 60}, {"crate": "smoltcp", "name": "<&T as core::fmt::Display>::fmt", "size": 60}, {"crate": "std", "name": "core::slice::::copy_from_slice::len_mismatch_fail", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_index_order_fail", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_end_index_len_fail", "size": 60}, {"crate": "std", "name": "core::panicking::panic_bounds_check", "size": 60}, {"crate": "std", "name": "core::slice::index::slice_start_index_len_fail", "size": 60}, {"crate": "std", "name": "core::ptr::drop_in_place>::init::{{closure}}>", "size": 60}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::atomic_waker::AtomicWaker::wake", "size": 58}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::Interface::socket_egress::{{closure}}", "size": 58}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::has_ip_addr", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 56}, {"crate": "embassy_executor", "name": "__pender", "size": 56}, {"crate": "std", "name": "core::panicking::panic_explicit", "size": 56}, {"name": "PIO0_IRQ_0", "size": 56}, {"crate": "heapless", "name": "heapless::deque::Deque::pop_front", "size": 56}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 56}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 56}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::Sender::try_send", "size": 56}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::register", "size": 54}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet<&T>::payload", "size": 52}, {"crate": "embassy_time", "name": "::poll", "size": 52}, {"crate": "embassy_rp", "name": "__pre_init", "size": 52}, {"crate": "embassy_rp", "name": "_embassy_time_set_alarm_callback", "size": 52}, {"crate": "embassy_net", "name": "embassy_net::to_smoltcp_hardware_address", "size": 52}, {"crate": "embassy_executor", "name": "embassy_executor::arch::thread::Executor::new", "size": 52}, {"crate": "std", "name": "core::slice::::split_at_mut", "size": 52}, {"crate": "std", "name": "core::option::expect_failed", "size": 52}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_reset::{{closure}}>", "size": 52}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::register", "size": 50}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Address::from_bytes", "size": 48}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 48}, {"crate": "smoltcp", "name": "defmt::traits::Format::_format_data", "size": 48}, {"crate": "smoltcp", "name": "::sub", "size": 48}, {"crate": "smoltcp", "name": ">::add", "size": 48}, {"crate": "embassy_hal_internal", "name": "embassy_hal_internal::interrupt::InterruptExt::set_priority", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_mutably_borrowed", "size": 48}, {"crate": "std", "name": "core::cell::panic_already_borrowed", "size": 48}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_src_addr", "size": 48}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_dst_addr", "size": 48}, {"crate": "tuf_no_std_common?", "name": "::fmt", "size": 48}, {"crate": "embassy_sync?", "name": "::fmt", "size": 48}, {"crate": "std", "name": "compiler_builtins::mem::memcmp", "size": 46}, {"name": "__aeabi_llsl", "size": 44}, {"crate": "byteorder", "name": "::write_u32", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::msg_type", "size": 44}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 44}, {"crate": "defmt", "name": "defmt::impls::primitives::::_format_data", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::ack_to_transmit", "size": 44}, {"crate": "embassy_rp", "name": "_embassy_time_allocate_alarm", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::set_ethertype", "size": 44}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 44}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::new_checked", "size": 42}, {"crate": "heapless?", "name": " as core::iter::traits::iterator::Iterator>::next", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_disable::{{closure}}>", "size": 42}, {"crate": "std", "name": "core::ptr::drop_in_place>::handle_irq::{{closure}}>", "size": 42}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&mut T>::data_mut", "size": 40}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 40}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_rem_by_zero", "size": 40}, {"crate": "byteorder", "name": "::write_u16", "size": 40}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 40}, {"crate": "cortex_m_rt", "name": "Reset", "size": 40}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Timer::should_keep_alive", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::contiguous_window", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::iface::interface::InterfaceInner::is_unicast_v4", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::window_to_update", "size": 38}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::wake", "size": 38}, {"crate": "embassy_net?", "name": "::clone", "size": 38}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_echo_ident", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_type", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::payload_mut", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::set_src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::set_checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::checksum", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_seq_no", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::echo_ident", "size": 36}, {"crate": "byteorder", "name": "::read_u16", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::src_port", "size": 36}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::reset", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::dst_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::udp::Packet::src_port", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::total_len", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::scaled_window", "size": 36}, {"crate": "embassy_time", "name": ">::add", "size": 36}, {"crate": "embassy_rp", "name": "::release", "size": 36}, {"crate": "embassy_net_driver", "name": "defmt::export::fmt_array", "size": 36}, {"crate": "std", "name": "::fmt", "size": 36}, {"crate": "std", "name": "core::panicking::panic_const::panic_const_async_fn_resumed", "size": 36}, {"crate": "std", "name": "core::panicking::panic", "size": 36}, {"crate": "bt4ot_ota_common", "name": "defmt::export::fmt", "size": 36}, {"crate": "embassy_net_driver_channel?", "name": " as embassy_net_driver::Driver>::hardware_address", "size": 36}, {"crate": "cortex_m", "name": "cortex_m::peripheral::scb::::sys_reset", "size": 36}, {"crate": "embassy_rp?", "name": " as core::future::future::Future>::poll", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 36}, {"crate": "std", "name": "core::ptr::drop_in_place>", "size": 36}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::set_pindir", "size": 36}, {"crate": "smoltcp", "name": "smoltcp::socket::tcp::Socket::set_state", "size": 34}, {"crate": "std", "name": "core::iter::traits::iterator::Iterator::nth", "size": 34}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 34}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Address::from_bytes", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::dst_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::src_addr", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet<&T>::data", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::header_len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::dst_addr", "size": 32}, {"crate": "heapless", "name": "heapless::deque::Deque::len", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::set_checksum", "size": 32}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_writen::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write::{{closure}}>", "size": 32}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read::{{closure}}>", "size": 32}, {"crate": "portable_atomic", "name": "portable_atomic::AtomicBool::compare_exchange", "size": 32}, {"crate": "embassy_time", "name": "embassy_time::timer::Timer::after_millis", "size": 32}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::multi_waker::MultiWakerRegistration<_>::wake", "size": 32}, {"crate": "embassy_rp", "name": "embassy_rp::pio::instr::exec_jmp", "size": 32}, {"crate": "smoltcp", "name": "smoltcp::wire::ip::checksum::combine", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Cidr::contains_addr", "size": 30}, {"crate": "no_std_net", "name": "core::slice::::copy_from_slice", "size": 30}, {"crate": "std", "name": "core::cell::RefCell::borrow_mut", "size": 30}, {"crate": "std", "name": "core::panicking::panic_fmt", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_readn::{{closure}}>", "size": 30}, {"crate": "std", "name": "core::ptr::drop_in_place>::log_init::{{closure}}>", "size": 30}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::advance", "size": 28}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 28}, {"crate": "critical_section", "name": "critical_section::with", "size": 28}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 28}, {"crate": "atomic_polyfill", "name": "atomic_polyfill::polyfill::AtomicU32::fetch_or", "size": 28}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 28}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 28}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::wait_for_event::{{closure}}>", "size": 28}, {"crate": "std", "name": "core::ptr::drop_in_place::{{closure}}>", "size": 28}, {"crate": "smoltcp", "name": "smoltcp::storage::ring_buffer::RingBuffer::get_idx_unchecked", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::parsers::Parser::accept_digit", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 26}, {"crate": "smoltcp", "name": "smoltcp::wire::icmpv4::Packet::set_msg_code", "size": 24}, {"crate": "smoltcp?", "name": "::fmt", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::protocol_len", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::arp::Packet::hardware_len", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ipv4::Packet::hop_limit", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame<&T>::payload", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_rp", "name": "embassy_rp::reset::unreset_wait", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_rp", "name": "defmt::export::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::Executor::spawn", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::state::State::update", "size": 24}, {"crate": "defmt", "name": "defmt::export::str", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "defmt", "name": "core::fmt::Write::write_fmt", "size": 24}, {"crate": "cyw43", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "std", "name": "::fmt", "size": 24}, {"crate": "smoltcp", "name": "smoltcp::wire::ethernet::Frame::payload_mut", "size": 24}, {"crate": "embassy_net_driver_channel?", "name": "::fmt", "size": 24}, {"crate": "embassy_executor?", "name": "::fmt", "size": 24}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 24}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 24}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 24}, {"crate": "embassy_executor", "name": "embassy_executor::raw::TaskStorage::poll", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 24}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index", "size": 24}, {"name": "__aeabi_uldivmod", "size": 22}, {"crate": "smoltcp", "name": "smoltcp::socket::waker::WakerRegistration::wake", "size": 22}, {"crate": "embassy_sync", "name": "embassy_sync::waitqueue::waker_registration::WakerRegistration::wake", "size": 22}, {"crate": "defmt_rtt", "name": "defmt_rtt::channel::Channel::nonblocking_write", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"crate": "std", "name": "core::array:: for [T; N]>::index", "size": 22}, {"crate": "std", "name": " as core::slice::index::SliceIndex<[T]>>::index_mut", "size": 22}, {"name": "HardFaultTrampoline", "size": 20}, {"crate": "hash32", "name": "<&T as core::fmt::Debug>::fmt", "size": 20}, {"crate": "embassy_rp", "name": "::now", "size": 20}, {"crate": "std", "name": "core::option::unwrap_failed", "size": 20}, {"crate": "std", "name": "core::fmt::Write::write_fmt", "size": 20}, {"crate": "std", "name": "core::array:: for [T; N]>::index_mut", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::backplane_set_window::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::check_status::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::core_is_up::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write32::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::wlan_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_read8::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::write16::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::writen::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::readn::{{closure}}>", "size": 20}, {"crate": "std", "name": "core::ptr::drop_in_place>::read8::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_write::{{closure}}>", "size": 20}, {"crate": "std?", "name": "core::ptr::drop_in_place< as cyw43::bus::SpiBusCyw43>::cmd_read::{{closure}}>", "size": 20}, {"crate": "std", "name": "<() as core::fmt::Debug>::fmt", "size": 20}, {"crate": "std", "name": "__udivmodsi4", "size": 18}, {"name": "__aeabi_uidivmod", "size": 18}, {"crate": "std", "name": "__udivmoddi4", "size": 18}, {"crate": "smoltcp", "name": "smoltcp::socket::dhcpv4::Socket::config_changed", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u64", "size": 18}, {"crate": "defmt", "name": "defmt::export::integers::u8", "size": 18}, {"crate": "defmt", "name": "defmt::export::istr", "size": 18}, {"crate": "benchmark_code_size_update_only", "name": "benchmark_code_size_update_only::__cortex_m_rt_main", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::read32_swapped::{{closure}},cyw43::bus::Bus>::init::{{closure}}::{{closure}}>>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::wait_for_event::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::bp_write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place>::write8::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::cmd_read::{{closure}}>", "size": 18}, {"crate": "std", "name": "core::ptr::drop_in_place::write::{{closure}}>", "size": 18}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memset", "size": 16}, {"crate": "smoltcp", "name": "smoltcp::wire::tcp::Repr::buffer_len", "size": 16}, {"crate": "smoltcp", "name": ">::add_assign", "size": 16}, {"crate": "smoltcp", "name": "defmt::export::fmt", "size": 16}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::pop_index", "size": 16}, {"crate": "embassy_executor", "name": "defmt::export::fmt", "size": 16}, {"crate": "defmt", "name": "defmt::encoding::inner::Encoder::write::{{closure}}", "size": 16}, {"crate": "defmt", "name": "defmt::export::integers::u32", "size": 16}, {"crate": "defmt", "name": "::write_str", "size": 16}, {"crate": "defmt", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Debug>::fmt", "size": 16}, {"crate": "std", "name": "<&T as core::fmt::Display>::fmt", "size": 16}, {"crate": "embassy_net?", "name": " as smoltcp::phy::Device>::capabilities", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "defmt", "name": "defmt::export::fmt", "size": 16}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 16}, {"crate": "std", "name": "core::fmt::Formatter::write_fmt", "size": 14}, {"crate": "embassy_time", "name": "_defmt_timestamp", "size": 14}, {"crate": "defmt", "name": "defmt::export::header", "size": 14}, {"name": "__Thumbv6MABSLongThunk__ZN10embassy_rp5flash11ram_helpers17write_flash_inner17h82c1bb1a60c73aa7E", "size": 12}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memclr8", "size": 12}, {"crate": "embassy_sync", "name": "embassy_sync::zerocopy_channel::State::push_index", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_0", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_1", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_2", "size": 12}, {"crate": "embassy_rp", "name": "TIMER_IRQ_3", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::clone", "size": 12}, {"crate": "embassy_executor", "name": "embassy_executor::raw::SyncExecutor::alarm_callback", "size": 12}, {"name": "__aeabi_memmove8", "size": 10}, {"name": "__aeabi_memcpy8", "size": 10}, {"name": "__aeabi_memclr4", "size": 10}, {"name": "__aeabi_memcpy4", "size": 10}, {"name": "__aeabi_memcpy", "size": 10}, {"name": "__aeabi_uidiv", "size": 10}, {"name": "memcmp", "size": 10}, {"name": "__aeabi_memset4", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memmove", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy8", "size": 10}, {"crate": "std", "name": "compiler_builtins::arm::__aeabi_memcpy", "size": 10}, {"name": "__aeabi_memclr8", "size": 10}, {"crate": "defmt", "name": "defmt::export::bool", "size": 10}, {"crate": "embassy_executor", "name": "embassy_executor::raw::util::UninitCell::write_in_place", "size": 10}, {"crate": "defmt", "name": "defmt::default_panic::panic_cold_explicit", "size": 8}, {"crate": "defmt", "name": "__defmt_default_panic", "size": 8}, {"name": "main", "size": 8}, {"crate": "cortex_m_rt", "name": "HardFault_", "size": 2}, {"crate": "embassy_executor", "name": "embassy_executor::raw::waker::drop", "size": 2}, {"crate": "defmt", "name": "__defmt_default_timestamp", "size": 2}, {"crate": "cortex_m_rt", "name": "DefaultHandler_", "size": 2}]}, "cargo_bloat_crates": {"file-size": 1031252, "text-section-size": 84740, "crates": [{"name": "smoltcp", "size": 25110}, {"name": "benchmark_code_size_update_only", "size": 17434}, {"name": "cyw43", "size": 10236}, {"name": "std", "size": 9998}, {"name": "embassy_executor", "size": 8928}, {"name": "digest", "size": 3460}, {"name": "embassy_net", "size": 2050}, {"name": "embassy_rp", "size": 1336}, {"name": "embassy_embedded_hal", "size": 1232}, {"name": "cyw43_pio", "size": 760}, {"name": "embassy_sync", "size": 622}, {"name": "defmt", "size": 606}, {"name": "sha2", "size": 468}, {"name": "defmt_rtt", "size": 466}, {"name": "[Unknown]", "size": 344}, {"name": "panic_probe", "size": 276}, {"name": "pio", "size": 244}, {"name": "embassy_boot", "size": 198}, {"name": "embassy_time", "size": 134}, {"name": "heapless", "size": 130}, {"name": "byteorder", "size": 120}, {"name": "bt4ot_ota_common", "size": 84}, {"name": "embassy_net_driver_channel", "size": 60}, {"name": "tuf_no_std_common", "size": 48}, {"name": "embassy_hal_internal", "size": 48}, {"name": "cortex_m_rt", "size": 44}, {"name": "cortex_m", "size": 36}, {"name": "embassy_net_driver", "size": 36}, {"name": "portable_atomic", "size": 32}, {"name": "no_std_net", "size": 30}, {"name": "critical_section", "size": 28}, {"name": "atomic_polyfill", "size": 28}, {"name": "hash32", "size": 20}]}}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:15:36.214852.json b/benchmark-output/performance-benchmark-2024-08-20 07:15:36.214852.json new file mode 100644 index 0000000..e023c66 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:15:36.214852.json @@ -0,0 +1 @@ +{"start_time":4441185,"end_time":16458552,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":84403000},"update_repo":{"secs":3,"nanos":65548000},"fetch_signature":{"secs":0,"nanos":27420000},"fetch_binary":{"secs":1,"nanos":254208000},"verify_binary":{"secs":2,"nanos":917122000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:16:39.421518.json b/benchmark-output/performance-benchmark-2024-08-20 07:16:39.421518.json new file mode 100644 index 0000000..a14cc42 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:16:39.421518.json @@ -0,0 +1 @@ +{"start_time":4395718,"end_time":17138315,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":55218000},"update_repo":{"secs":3,"nanos":34535000},"fetch_signature":{"secs":0,"nanos":25820000},"fetch_binary":{"secs":2,"nanos":53178000},"verify_binary":{"secs":2,"nanos":916978000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:17:33.877914.json b/benchmark-output/performance-benchmark-2024-08-20 07:17:33.877914.json new file mode 100644 index 0000000..b4d2b33 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:17:33.877914.json @@ -0,0 +1 @@ +{"start_time":4434554,"end_time":17890433,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":55187000},"update_repo":{"secs":3,"nanos":753057000},"fetch_signature":{"secs":0,"nanos":23908000},"fetch_binary":{"secs":2,"nanos":37076000},"verify_binary":{"secs":2,"nanos":917126000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:18:27.641227.json b/benchmark-output/performance-benchmark-2024-08-20 07:18:27.641227.json new file mode 100644 index 0000000..270d415 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:18:27.641227.json @@ -0,0 +1 @@ +{"start_time":4436082,"end_time":17160847,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":62738000},"update_repo":{"secs":3,"nanos":784426000},"fetch_signature":{"secs":0,"nanos":25617000},"fetch_binary":{"secs":1,"nanos":267674000},"verify_binary":{"secs":2,"nanos":916949000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:19:22.223523.json b/benchmark-output/performance-benchmark-2024-08-20 07:19:22.223523.json new file mode 100644 index 0000000..4a4633a --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:19:22.223523.json @@ -0,0 +1 @@ +{"start_time":4393016,"end_time":17955695,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":82345000},"update_repo":{"secs":3,"nanos":783756000},"fetch_signature":{"secs":0,"nanos":26647000},"fetch_binary":{"secs":2,"nanos":88228000},"verify_binary":{"secs":2,"nanos":916938000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:22:22.761514.json b/benchmark-output/performance-benchmark-2024-08-20 07:22:22.761514.json new file mode 100644 index 0000000..3d86f24 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:22:22.761514.json @@ -0,0 +1 @@ +{"start_time":4446383,"end_time":18036709,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":84216000},"update_repo":{"secs":4,"nanos":91252000},"fetch_signature":{"secs":0,"nanos":26625000},"fetch_binary":{"secs":1,"nanos":805855000},"verify_binary":{"secs":2,"nanos":917332000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:23:17.150873.json b/benchmark-output/performance-benchmark-2024-08-20 07:23:17.150873.json new file mode 100644 index 0000000..9d0b1e5 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:23:17.150873.json @@ -0,0 +1 @@ +{"start_time":4411479,"end_time":17748179,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":76761000},"update_repo":{"secs":3,"nanos":791243000},"fetch_signature":{"secs":0,"nanos":25880000},"fetch_binary":{"secs":1,"nanos":878900000},"verify_binary":{"secs":2,"nanos":917305000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:24:12.539264.json b/benchmark-output/performance-benchmark-2024-08-20 07:24:12.539264.json new file mode 100644 index 0000000..5e216d4 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:24:12.539264.json @@ -0,0 +1 @@ +{"start_time":4393258,"end_time":18717362,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":84714000},"update_repo":{"secs":4,"nanos":810419000},"fetch_signature":{"secs":0,"nanos":26528000},"fetch_binary":{"secs":1,"nanos":833615000},"verify_binary":{"secs":2,"nanos":917247000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:25:08.425179.json b/benchmark-output/performance-benchmark-2024-08-20 07:25:08.425179.json new file mode 100644 index 0000000..a6eeb7e --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:25:08.425179.json @@ -0,0 +1 @@ +{"start_time":4398741,"end_time":18713211,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":95037000},"update_repo":{"secs":4,"nanos":808650000},"fetch_signature":{"secs":0,"nanos":28306000},"fetch_binary":{"secs":1,"nanos":799234000},"verify_binary":{"secs":2,"nanos":917295000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:26:04.337105.json b/benchmark-output/performance-benchmark-2024-08-20 07:26:04.337105.json new file mode 100644 index 0000000..2e0508d --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:26:04.337105.json @@ -0,0 +1 @@ +{"start_time":4426171,"end_time":19251050,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":89613000},"update_repo":{"secs":4,"nanos":811238000},"fetch_signature":{"secs":0,"nanos":26880000},"fetch_binary":{"secs":2,"nanos":331732000},"verify_binary":{"secs":2,"nanos":917345000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:27:00.148576.json b/benchmark-output/performance-benchmark-2024-08-20 07:27:00.148576.json new file mode 100644 index 0000000..8b23fda --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:27:00.148576.json @@ -0,0 +1 @@ +{"start_time":4471740,"end_time":18983180,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":92505000},"update_repo":{"secs":3,"nanos":777270000},"fetch_signature":{"secs":0,"nanos":28562000},"fetch_binary":{"secs":3,"nanos":40432000},"verify_binary":{"secs":2,"nanos":917401000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:27:55.237711.json b/benchmark-output/performance-benchmark-2024-08-20 07:27:55.237711.json new file mode 100644 index 0000000..ac5b5f2 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:27:55.237711.json @@ -0,0 +1 @@ +{"start_time":4449730,"end_time":18273713,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":83366000},"update_repo":{"secs":3,"nanos":785555000},"fetch_signature":{"secs":0,"nanos":25751000},"fetch_binary":{"secs":2,"nanos":369321000},"verify_binary":{"secs":2,"nanos":917490000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:28:50.945571.json b/benchmark-output/performance-benchmark-2024-08-20 07:28:50.945571.json new file mode 100644 index 0000000..bdd23fd --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:28:50.945571.json @@ -0,0 +1 @@ +{"start_time":4440227,"end_time":18869666,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":82032000},"update_repo":{"secs":3,"nanos":755653000},"fetch_signature":{"secs":0,"nanos":24815000},"fetch_binary":{"secs":2,"nanos":984310000},"verify_binary":{"secs":2,"nanos":917428000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:29:46.754391.json b/benchmark-output/performance-benchmark-2024-08-20 07:29:46.754391.json new file mode 100644 index 0000000..d87e095 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:29:46.754391.json @@ -0,0 +1 @@ +{"start_time":4431775,"end_time":18972493,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":55518000},"update_repo":{"secs":3,"nanos":851820000},"fetch_signature":{"secs":0,"nanos":24019000},"fetch_binary":{"secs":3,"nanos":19667000},"verify_binary":{"secs":2,"nanos":917367000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:30:40.412155.json b/benchmark-output/performance-benchmark-2024-08-20 07:30:40.412155.json new file mode 100644 index 0000000..c309417 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:30:40.412155.json @@ -0,0 +1 @@ +{"start_time":4446647,"end_time":16807391,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":78852000},"update_repo":{"secs":3,"nanos":767037000},"fetch_signature":{"secs":0,"nanos":24518000},"fetch_binary":{"secs":0,"nanos":917692000},"verify_binary":{"secs":2,"nanos":917263000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:31:35.143103.json b/benchmark-output/performance-benchmark-2024-08-20 07:31:35.143103.json new file mode 100644 index 0000000..c22488e --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:31:35.143103.json @@ -0,0 +1 @@ +{"start_time":4391460,"end_time":18392905,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":58843000},"update_repo":{"secs":4,"nanos":762034000},"fetch_signature":{"secs":0,"nanos":24822000},"fetch_binary":{"secs":1,"nanos":591189000},"verify_binary":{"secs":2,"nanos":917315000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:32:28.985440.json b/benchmark-output/performance-benchmark-2024-08-20 07:32:28.985440.json new file mode 100644 index 0000000..50acfa7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:32:28.985440.json @@ -0,0 +1 @@ +{"start_time":4437488,"end_time":17493901,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":87330000},"update_repo":{"secs":3,"nanos":786108000},"fetch_signature":{"secs":0,"nanos":27062000},"fetch_binary":{"secs":1,"nanos":563285000},"verify_binary":{"secs":2,"nanos":917376000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:33:22.107728.json b/benchmark-output/performance-benchmark-2024-08-20 07:33:22.107728.json new file mode 100644 index 0000000..76b2a11 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:33:22.107728.json @@ -0,0 +1 @@ +{"start_time":4414715,"end_time":16704938,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":86221000},"update_repo":{"secs":3,"nanos":787027000},"fetch_signature":{"secs":0,"nanos":28421000},"fetch_binary":{"secs":0,"nanos":819779000},"verify_binary":{"secs":2,"nanos":917324000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:34:16.072524.json b/benchmark-output/performance-benchmark-2024-08-20 07:34:16.072524.json new file mode 100644 index 0000000..9760595 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:34:16.072524.json @@ -0,0 +1 @@ +{"start_time":4450529,"end_time":17526172,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":54040000},"update_repo":{"secs":3,"nanos":788704000},"fetch_signature":{"secs":0,"nanos":28923000},"fetch_binary":{"secs":1,"nanos":614286000},"verify_binary":{"secs":2,"nanos":917248000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:36:24.696037.json b/benchmark-output/performance-benchmark-2024-08-20 07:36:24.696037.json new file mode 100644 index 0000000..348874e --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:36:24.696037.json @@ -0,0 +1 @@ +{"start_time":4476221,"end_time":15513377,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":78240000},"update_repo":{"secs":3,"nanos":76938000},"fetch_signature":{"secs":0,"nanos":27392000},"fetch_binary":{"secs":0,"nanos":335845000},"verify_binary":{"secs":2,"nanos":918183000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:37:17.733229.json b/benchmark-output/performance-benchmark-2024-08-20 07:37:17.733229.json new file mode 100644 index 0000000..efec3da --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:37:17.733229.json @@ -0,0 +1 @@ +{"start_time":4466519,"end_time":16961718,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":58321000},"update_repo":{"secs":3,"nanos":758177000},"fetch_signature":{"secs":0,"nanos":23990000},"fetch_binary":{"secs":1,"nanos":138777000},"verify_binary":{"secs":2,"nanos":918230000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:38:10.775495.json b/benchmark-output/performance-benchmark-2024-08-20 07:38:10.775495.json new file mode 100644 index 0000000..fed90a0 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:38:10.775495.json @@ -0,0 +1 @@ +{"start_time":4413188,"end_time":16904066,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":84188000},"update_repo":{"secs":3,"nanos":789471000},"fetch_signature":{"secs":0,"nanos":24181000},"fetch_binary":{"secs":1,"nanos":75226000},"verify_binary":{"secs":2,"nanos":918244000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:39:03.822099.json b/benchmark-output/performance-benchmark-2024-08-20 07:39:03.822099.json new file mode 100644 index 0000000..0b904b1 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:39:03.822099.json @@ -0,0 +1 @@ +{"start_time":4403018,"end_time":16897237,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":113537000},"update_repo":{"secs":3,"nanos":757623000},"fetch_signature":{"secs":0,"nanos":26103000},"fetch_binary":{"secs":1,"nanos":67313000},"verify_binary":{"secs":2,"nanos":918213000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 07:39:56.865710.json b/benchmark-output/performance-benchmark-2024-08-20 07:39:56.865710.json new file mode 100644 index 0000000..5eecee1 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 07:39:56.865710.json @@ -0,0 +1 @@ +{"start_time":4418158,"end_time":16912471,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":88791000},"update_repo":{"secs":3,"nanos":790205000},"fetch_signature":{"secs":0,"nanos":24817000},"fetch_binary":{"secs":1,"nanos":61199000},"verify_binary":{"secs":2,"nanos":918214000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:28:50.463366.json b/benchmark-output/performance-benchmark-2024-08-20 08:28:50.463366.json new file mode 100644 index 0000000..efdceeb --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:28:50.463366.json @@ -0,0 +1 @@ +{"start_time":4418280,"end_time":15483283,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":83616000},"update_repo":{"secs":3,"nanos":84023000},"fetch_signature":{"secs":0,"nanos":25621000},"fetch_binary":{"secs":0,"nanos":342537000},"verify_binary":{"secs":2,"nanos":918321000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:29:42.892983.json b/benchmark-output/performance-benchmark-2024-08-20 08:29:42.892983.json new file mode 100644 index 0000000..f0a26b9 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:29:42.892983.json @@ -0,0 +1 @@ +{"start_time":4419111,"end_time":16315821,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":83052000},"update_repo":{"secs":3,"nanos":803999000},"fetch_signature":{"secs":0,"nanos":25527000},"fetch_binary":{"secs":0,"nanos":439961000},"verify_binary":{"secs":2,"nanos":918178000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:30:36.962670.json b/benchmark-output/performance-benchmark-2024-08-20 08:30:36.962670.json new file mode 100644 index 0000000..856cd1c --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:30:36.962670.json @@ -0,0 +1 @@ +{"start_time":4388161,"end_time":17862446,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":63475000},"update_repo":{"secs":4,"nanos":826562000},"fetch_signature":{"secs":0,"nanos":23692000},"fetch_binary":{"secs":1,"nanos":32990000},"verify_binary":{"secs":2,"nanos":918141000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:31:31.136874.json b/benchmark-output/performance-benchmark-2024-08-20 08:31:31.136874.json new file mode 100644 index 0000000..a5d53a3 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:31:31.136874.json @@ -0,0 +1 @@ +{"start_time":4385713,"end_time":18019086,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":94991000},"update_repo":{"secs":4,"nanos":827024000},"fetch_signature":{"secs":0,"nanos":22641000},"fetch_binary":{"secs":1,"nanos":132877000},"verify_binary":{"secs":2,"nanos":918050000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:32:24.280363.json b/benchmark-output/performance-benchmark-2024-08-20 08:32:24.280363.json new file mode 100644 index 0000000..ed44df7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:32:24.280363.json @@ -0,0 +1 @@ +{"start_time":4428184,"end_time":16929156,"binary_size":41440,"fetch_target_files":{"secs":0,"nanos":83828000},"update_repo":{"secs":3,"nanos":802930000},"fetch_signature":{"secs":0,"nanos":27858000},"fetch_binary":{"secs":1,"nanos":32674000},"verify_binary":{"secs":2,"nanos":918153000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:34:02.378899.json b/benchmark-output/performance-benchmark-2024-08-20 08:34:02.378899.json new file mode 100644 index 0000000..85a7701 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:34:02.378899.json @@ -0,0 +1 @@ +{"start_time":4424849,"end_time":16083735,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":83795000},"update_repo":{"secs":3,"nanos":86140000},"fetch_signature":{"secs":0,"nanos":23764000},"fetch_binary":{"secs":0,"nanos":882224000},"verify_binary":{"secs":2,"nanos":917397000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:34:55.625728.json b/benchmark-output/performance-benchmark-2024-08-20 08:34:55.625728.json new file mode 100644 index 0000000..f796ce3 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:34:55.625728.json @@ -0,0 +1 @@ +{"start_time":4424352,"end_time":16746630,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":83943000},"update_repo":{"secs":3,"nanos":804049000},"fetch_signature":{"secs":0,"nanos":30813000},"fetch_binary":{"secs":0,"nanos":802845000},"verify_binary":{"secs":2,"nanos":917216000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:35:48.872563.json b/benchmark-output/performance-benchmark-2024-08-20 08:35:48.872563.json new file mode 100644 index 0000000..2e6a254 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:35:48.872563.json @@ -0,0 +1 @@ +{"start_time":4426659,"end_time":16797195,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":87096000},"update_repo":{"secs":3,"nanos":803320000},"fetch_signature":{"secs":0,"nanos":23648000},"fetch_binary":{"secs":0,"nanos":862730000},"verify_binary":{"secs":2,"nanos":917284000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:36:43.145201.json b/benchmark-output/performance-benchmark-2024-08-20 08:36:43.145201.json new file mode 100644 index 0000000..20132cd --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:36:43.145201.json @@ -0,0 +1 @@ +{"start_time":4393060,"end_time":17778386,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":54255000},"update_repo":{"secs":4,"nanos":840007000},"fetch_signature":{"secs":0,"nanos":23989000},"fetch_binary":{"secs":0,"nanos":870011000},"verify_binary":{"secs":2,"nanos":917351000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:37:36.392582.json b/benchmark-output/performance-benchmark-2024-08-20 08:37:36.392582.json new file mode 100644 index 0000000..e963546 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:37:36.392582.json @@ -0,0 +1 @@ +{"start_time":4415000,"end_time":16791819,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":86277000},"update_repo":{"secs":3,"nanos":801744000},"fetch_signature":{"secs":0,"nanos":23849000},"fetch_binary":{"secs":0,"nanos":871892000},"verify_binary":{"secs":2,"nanos":917399000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:38:29.743328.json b/benchmark-output/performance-benchmark-2024-08-20 08:38:29.743328.json new file mode 100644 index 0000000..53d4fba --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:38:29.743328.json @@ -0,0 +1 @@ +{"start_time":4503412,"end_time":16879257,"binary_size":106992,"fetch_target_files":{"secs":0,"nanos":110872000},"update_repo":{"secs":3,"nanos":776025000},"fetch_signature":{"secs":0,"nanos":24223000},"fetch_binary":{"secs":0,"nanos":874702000},"verify_binary":{"secs":2,"nanos":917241000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:39:24.220492.json b/benchmark-output/performance-benchmark-2024-08-20 08:39:24.220492.json new file mode 100644 index 0000000..cb180ec --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:39:24.220492.json @@ -0,0 +1 @@ +{"start_time":4426401,"end_time":18011158,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":112626000},"update_repo":{"secs":3,"nanos":775555000},"fetch_signature":{"secs":0,"nanos":27108000},"fetch_binary":{"secs":2,"nanos":69017000},"verify_binary":{"secs":2,"nanos":915839000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:40:18.185840.json b/benchmark-output/performance-benchmark-2024-08-20 08:40:18.185840.json new file mode 100644 index 0000000..8af8442 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:40:18.185840.json @@ -0,0 +1 @@ +{"start_time":4474880,"end_time":17321495,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":84009000},"update_repo":{"secs":3,"nanos":803187000},"fetch_signature":{"secs":0,"nanos":25172000},"fetch_binary":{"secs":1,"nanos":340138000},"verify_binary":{"secs":2,"nanos":915854000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:41:12.867675.json b/benchmark-output/performance-benchmark-2024-08-20 08:41:12.867675.json new file mode 100644 index 0000000..df2a7b0 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:41:12.867675.json @@ -0,0 +1 @@ +{"start_time":4422796,"end_time":18008737,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":81835000},"update_repo":{"secs":3,"nanos":805091000},"fetch_signature":{"secs":0,"nanos":25228000},"fetch_binary":{"secs":2,"nanos":66434000},"verify_binary":{"secs":2,"nanos":915819000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:42:07.437976.json b/benchmark-output/performance-benchmark-2024-08-20 08:42:07.437976.json new file mode 100644 index 0000000..6d2e308 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:42:07.437976.json @@ -0,0 +1 @@ +{"start_time":4448376,"end_time":18002815,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":91068000},"update_repo":{"secs":3,"nanos":805180000},"fetch_signature":{"secs":0,"nanos":23494000},"fetch_binary":{"secs":2,"nanos":18034000},"verify_binary":{"secs":2,"nanos":915812000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:43:02.032717.json b/benchmark-output/performance-benchmark-2024-08-20 08:43:02.032717.json new file mode 100644 index 0000000..7b7775a --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:43:02.032717.json @@ -0,0 +1 @@ +{"start_time":4427464,"end_time":17932210,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":84325000},"update_repo":{"secs":3,"nanos":805799000},"fetch_signature":{"secs":0,"nanos":24156000},"fetch_binary":{"secs":1,"nanos":997783000},"verify_binary":{"secs":2,"nanos":915815000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:43:55.949029.json b/benchmark-output/performance-benchmark-2024-08-20 08:43:55.949029.json new file mode 100644 index 0000000..b15da7c --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:43:55.949029.json @@ -0,0 +1 @@ +{"start_time":4445267,"end_time":17305885,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":57131000},"update_repo":{"secs":3,"nanos":805397000},"fetch_signature":{"secs":0,"nanos":23258000},"fetch_binary":{"secs":1,"nanos":357450000},"verify_binary":{"secs":2,"nanos":915870000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:44:49.855015.json b/benchmark-output/performance-benchmark-2024-08-20 08:44:49.855015.json new file mode 100644 index 0000000..4d4a6cb --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:44:49.855015.json @@ -0,0 +1 @@ +{"start_time":4428705,"end_time":17256810,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":84700000},"update_repo":{"secs":3,"nanos":806351000},"fetch_signature":{"secs":0,"nanos":22685000},"fetch_binary":{"secs":1,"nanos":290192000},"verify_binary":{"secs":2,"nanos":915804000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:45:44.434503.json b/benchmark-output/performance-benchmark-2024-08-20 08:45:44.434503.json new file mode 100644 index 0000000..95357e7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:45:44.434503.json @@ -0,0 +1 @@ +{"start_time":4428585,"end_time":17955783,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":83883000},"update_repo":{"secs":3,"nanos":806026000},"fetch_signature":{"secs":0,"nanos":25576000},"fetch_binary":{"secs":2,"nanos":1739000},"verify_binary":{"secs":2,"nanos":915920000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:46:39.117894.json b/benchmark-output/performance-benchmark-2024-08-20 08:46:39.117894.json new file mode 100644 index 0000000..a31e260 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:46:39.117894.json @@ -0,0 +1 @@ +{"start_time":4463980,"end_time":18056441,"binary_size":172520,"fetch_target_files":{"secs":0,"nanos":81985000},"update_repo":{"secs":3,"nanos":806824000},"fetch_signature":{"secs":0,"nanos":24527000},"fetch_binary":{"secs":2,"nanos":57031000},"verify_binary":{"secs":2,"nanos":915894000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:47:34.209877.json b/benchmark-output/performance-benchmark-2024-08-20 08:47:34.209877.json new file mode 100644 index 0000000..0cced91 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:47:34.209877.json @@ -0,0 +1 @@ +{"start_time":4417556,"end_time":18384177,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":84091000},"update_repo":{"secs":3,"nanos":806509000},"fetch_signature":{"secs":0,"nanos":23351000},"fetch_binary":{"secs":2,"nanos":439917000},"verify_binary":{"secs":2,"nanos":916337000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:48:28.789175.json b/benchmark-output/performance-benchmark-2024-08-20 08:48:28.789175.json new file mode 100644 index 0000000..548f4fd --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:48:28.789175.json @@ -0,0 +1 @@ +{"start_time":4419398,"end_time":17766486,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":85800000},"update_repo":{"secs":3,"nanos":806128000},"fetch_signature":{"secs":0,"nanos":26476000},"fetch_binary":{"secs":1,"nanos":800608000},"verify_binary":{"secs":2,"nanos":916378000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:49:24.085695.json b/benchmark-output/performance-benchmark-2024-08-20 08:49:24.085695.json new file mode 100644 index 0000000..a9e4b02 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:49:24.085695.json @@ -0,0 +1 @@ +{"start_time":4427502,"end_time":18493535,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":82647000},"update_repo":{"secs":3,"nanos":807278000},"fetch_signature":{"secs":0,"nanos":24466000},"fetch_binary":{"secs":2,"nanos":531167000},"verify_binary":{"secs":2,"nanos":916351000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:50:19.382249.json b/benchmark-output/performance-benchmark-2024-08-20 08:50:19.382249.json new file mode 100644 index 0000000..d42288f --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:50:19.382249.json @@ -0,0 +1 @@ +{"start_time":4448377,"end_time":18505752,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":98545000},"update_repo":{"secs":3,"nanos":793294000},"fetch_signature":{"secs":0,"nanos":24278000},"fetch_binary":{"secs":2,"nanos":532835000},"verify_binary":{"secs":2,"nanos":916356000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:51:14.650965.json b/benchmark-output/performance-benchmark-2024-08-20 08:51:14.650965.json new file mode 100644 index 0000000..b63b398 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:51:14.650965.json @@ -0,0 +1 @@ +{"start_time":4420939,"end_time":18471392,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":63890000},"update_repo":{"secs":3,"nanos":807568000},"fetch_signature":{"secs":0,"nanos":26426000},"fetch_binary":{"secs":2,"nanos":523504000},"verify_binary":{"secs":2,"nanos":916400000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:52:10.182122.json b/benchmark-output/performance-benchmark-2024-08-20 08:52:10.182122.json new file mode 100644 index 0000000..f8f3635 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:52:10.182122.json @@ -0,0 +1 @@ +{"start_time":4389156,"end_time":18757982,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":86901000},"update_repo":{"secs":4,"nanos":833356000},"fetch_signature":{"secs":0,"nanos":23748000},"fetch_binary":{"secs":1,"nanos":804081000},"verify_binary":{"secs":2,"nanos":916452000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:53:04.763449.json b/benchmark-output/performance-benchmark-2024-08-20 08:53:04.763449.json new file mode 100644 index 0000000..624ef3f --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:53:04.763449.json @@ -0,0 +1 @@ +{"start_time":4450536,"end_time":17799980,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":82251000},"update_repo":{"secs":3,"nanos":810398000},"fetch_signature":{"secs":0,"nanos":26313000},"fetch_binary":{"secs":1,"nanos":832350000},"verify_binary":{"secs":2,"nanos":916271000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:54:00.160700.json b/benchmark-output/performance-benchmark-2024-08-20 08:54:00.160700.json new file mode 100644 index 0000000..60de86b --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:54:00.160700.json @@ -0,0 +1 @@ +{"start_time":4465848,"end_time":18538497,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":54989000},"update_repo":{"secs":3,"nanos":809186000},"fetch_signature":{"secs":0,"nanos":28715000},"fetch_binary":{"secs":2,"nanos":560813000},"verify_binary":{"secs":2,"nanos":916485000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:54:54.841759.json b/benchmark-output/performance-benchmark-2024-08-20 08:54:54.841759.json new file mode 100644 index 0000000..0000a8b --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:54:54.841759.json @@ -0,0 +1 @@ +{"start_time":4451997,"end_time":17808394,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":81301000},"update_repo":{"secs":3,"nanos":815036000},"fetch_signature":{"secs":0,"nanos":24932000},"fetch_binary":{"secs":1,"nanos":816302000},"verify_binary":{"secs":2,"nanos":916358000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:55:50.241128.json b/benchmark-output/performance-benchmark-2024-08-20 08:55:50.241128.json new file mode 100644 index 0000000..c756f43 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:55:50.241128.json @@ -0,0 +1 @@ +{"start_time":4460472,"end_time":18532159,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":82784000},"update_repo":{"secs":3,"nanos":809209000},"fetch_signature":{"secs":0,"nanos":23595000},"fetch_binary":{"secs":2,"nanos":534603000},"verify_binary":{"secs":2,"nanos":916362000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:56:45.536299.json b/benchmark-output/performance-benchmark-2024-08-20 08:56:45.536299.json new file mode 100644 index 0000000..a0d5cd1 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:56:45.536299.json @@ -0,0 +1 @@ +{"start_time":4426574,"end_time":18483084,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":95857000},"update_repo":{"secs":3,"nanos":810532000},"fetch_signature":{"secs":0,"nanos":24451000},"fetch_binary":{"secs":2,"nanos":488641000},"verify_binary":{"secs":2,"nanos":916469000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:57:40.118298.json b/benchmark-output/performance-benchmark-2024-08-20 08:57:40.118298.json new file mode 100644 index 0000000..ea07692 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:57:40.118298.json @@ -0,0 +1 @@ +{"start_time":4462076,"end_time":17784949,"binary_size":238060,"fetch_target_files":{"secs":0,"nanos":109916000},"update_repo":{"secs":3,"nanos":786351000},"fetch_signature":{"secs":0,"nanos":24462000},"fetch_binary":{"secs":1,"nanos":787092000},"verify_binary":{"secs":2,"nanos":916420000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:58:35.186132.json b/benchmark-output/performance-benchmark-2024-08-20 08:58:35.186132.json new file mode 100644 index 0000000..ef85687 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:58:35.186132.json @@ -0,0 +1 @@ +{"start_time":4479335,"end_time":18329084,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":105688000},"update_repo":{"secs":3,"nanos":790567000},"fetch_signature":{"secs":0,"nanos":24061000},"fetch_binary":{"secs":2,"nanos":325375000},"verify_binary":{"secs":2,"nanos":916948000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 08:59:31.019219.json b/benchmark-output/performance-benchmark-2024-08-20 08:59:31.019219.json new file mode 100644 index 0000000..6746dbe --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 08:59:31.019219.json @@ -0,0 +1 @@ +{"start_time":4443597,"end_time":18921801,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":55096000},"update_repo":{"secs":3,"nanos":809854000},"fetch_signature":{"secs":0,"nanos":23272000},"fetch_binary":{"secs":2,"nanos":971757000},"verify_binary":{"secs":2,"nanos":917001000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:00:27.237984.json b/benchmark-output/performance-benchmark-2024-08-20 09:00:27.237984.json new file mode 100644 index 0000000..589f877 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:00:27.237984.json @@ -0,0 +1 @@ +{"start_time":4450609,"end_time":19312859,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":57207000},"update_repo":{"secs":4,"nanos":833306000},"fetch_signature":{"secs":0,"nanos":26874000},"fetch_binary":{"secs":2,"nanos":350826000},"verify_binary":{"secs":2,"nanos":916974000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:01:23.149472.json b/benchmark-output/performance-benchmark-2024-08-20 09:01:23.149472.json new file mode 100644 index 0000000..90c3caf --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:01:23.149472.json @@ -0,0 +1 @@ +{"start_time":4434776,"end_time":18993833,"binary_size":303592,"fetch_target_files":{"secs":0,"nanos":86691000},"update_repo":{"secs":3,"nanos":810831000},"fetch_signature":{"secs":0,"nanos":26773000},"fetch_binary":{"secs":3,"nanos":15980000},"verify_binary":{"secs":2,"nanos":916946000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:02:19.674217.json b/benchmark-output/performance-benchmark-2024-08-20 09:02:19.674217.json new file mode 100644 index 0000000..fb63b92 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:02:19.674217.json @@ -0,0 +1 @@ +{"start_time":4477666,"end_time":19474759,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":86294000},"update_repo":{"secs":3,"nanos":810036000},"fetch_signature":{"secs":0,"nanos":25671000},"fetch_binary":{"secs":3,"nanos":448716000},"verify_binary":{"secs":2,"nanos":916578000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:03:24.598129.json b/benchmark-output/performance-benchmark-2024-08-20 09:03:24.598129.json new file mode 100644 index 0000000..ae43a15 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:03:24.598129.json @@ -0,0 +1 @@ +{"start_time":4439013,"end_time":18121998,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":84940000},"update_repo":{"secs":3,"nanos":196927000},"fetch_signature":{"secs":0,"nanos":25201000},"fetch_binary":{"secs":2,"nanos":767413000},"verify_binary":{"secs":2,"nanos":916610000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:04:21.226169.json b/benchmark-output/performance-benchmark-2024-08-20 09:04:21.226169.json new file mode 100644 index 0000000..4250020 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:04:21.226169.json @@ -0,0 +1 @@ +{"start_time":4415655,"end_time":19442418,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":85874000},"update_repo":{"secs":3,"nanos":804816000},"fetch_signature":{"secs":0,"nanos":23160000},"fetch_binary":{"secs":3,"nanos":492996000},"verify_binary":{"secs":2,"nanos":916772000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:05:17.137034.json b/benchmark-output/performance-benchmark-2024-08-20 09:05:17.137034.json new file mode 100644 index 0000000..f071cf1 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:05:17.137034.json @@ -0,0 +1 @@ +{"start_time":4421575,"end_time":18736254,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":84640000},"update_repo":{"secs":3,"nanos":810736000},"fetch_signature":{"secs":0,"nanos":26118000},"fetch_binary":{"secs":2,"nanos":759790000},"verify_binary":{"secs":2,"nanos":916748000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:06:13.048665.json b/benchmark-output/performance-benchmark-2024-08-20 09:06:13.048665.json new file mode 100644 index 0000000..da9644c --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:06:13.048665.json @@ -0,0 +1 @@ +{"start_time":4400414,"end_time":18720400,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":56447000},"update_repo":{"secs":3,"nanos":778447000},"fetch_signature":{"secs":0,"nanos":24811000},"fetch_binary":{"secs":2,"nanos":844935000},"verify_binary":{"secs":2,"nanos":916643000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:07:09.061002.json b/benchmark-output/performance-benchmark-2024-08-20 09:07:09.061002.json new file mode 100644 index 0000000..c4d273a --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:07:09.061002.json @@ -0,0 +1 @@ +{"start_time":4407023,"end_time":18844305,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":89660000},"update_repo":{"secs":3,"nanos":813486000},"fetch_signature":{"secs":0,"nanos":26095000},"fetch_binary":{"secs":2,"nanos":849283000},"verify_binary":{"secs":2,"nanos":916748000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:08:05.015132.json b/benchmark-output/performance-benchmark-2024-08-20 09:08:05.015132.json new file mode 100644 index 0000000..bdb366d --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:08:05.015132.json @@ -0,0 +1 @@ +{"start_time":4444224,"end_time":18877700,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":97872000},"update_repo":{"secs":3,"nanos":816520000},"fetch_signature":{"secs":0,"nanos":26693000},"fetch_binary":{"secs":2,"nanos":863839000},"verify_binary":{"secs":2,"nanos":916704000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:09:00.881587.json b/benchmark-output/performance-benchmark-2024-08-20 09:09:00.881587.json new file mode 100644 index 0000000..30fc771 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:09:00.881587.json @@ -0,0 +1 @@ +{"start_time":4414753,"end_time":18742225,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":87127000},"update_repo":{"secs":3,"nanos":813189000},"fetch_signature":{"secs":0,"nanos":25311000},"fetch_binary":{"secs":2,"nanos":777237000},"verify_binary":{"secs":2,"nanos":916696000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:09:57.487831.json b/benchmark-output/performance-benchmark-2024-08-20 09:09:57.487831.json new file mode 100644 index 0000000..4a786bc --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:09:57.487831.json @@ -0,0 +1 @@ +{"start_time":4433635,"end_time":19487904,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":87704000},"update_repo":{"secs":3,"nanos":812718000},"fetch_signature":{"secs":0,"nanos":26307000},"fetch_binary":{"secs":3,"nanos":503253000},"verify_binary":{"secs":2,"nanos":916695000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:10:54.033808.json b/benchmark-output/performance-benchmark-2024-08-20 09:10:54.033808.json new file mode 100644 index 0000000..53339f7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:10:54.033808.json @@ -0,0 +1 @@ +{"start_time":4467698,"end_time":19464342,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":87498000},"update_repo":{"secs":3,"nanos":812634000},"fetch_signature":{"secs":0,"nanos":26191000},"fetch_binary":{"secs":3,"nanos":432831000},"verify_binary":{"secs":2,"nanos":916636000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:11:50.558019.json b/benchmark-output/performance-benchmark-2024-08-20 09:11:50.558019.json new file mode 100644 index 0000000..25f0bc6 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:11:50.558019.json @@ -0,0 +1 @@ +{"start_time":4424396,"end_time":19390598,"binary_size":369132,"fetch_target_files":{"secs":0,"nanos":53260000},"update_repo":{"secs":3,"nanos":813931000},"fetch_signature":{"secs":0,"nanos":27063000},"fetch_binary":{"secs":3,"nanos":440748000},"verify_binary":{"secs":2,"nanos":916684000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:12:47.697540.json b/benchmark-output/performance-benchmark-2024-08-20 09:12:47.697540.json new file mode 100644 index 0000000..e11729a --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:12:47.697540.json @@ -0,0 +1 @@ +{"start_time":4439226,"end_time":19952287,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":87083000},"update_repo":{"secs":3,"nanos":813104000},"fetch_signature":{"secs":0,"nanos":23473000},"fetch_binary":{"secs":3,"nanos":952907000},"verify_binary":{"secs":2,"nanos":915930000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:13:44.324380.json b/benchmark-output/performance-benchmark-2024-08-20 09:13:44.324380.json new file mode 100644 index 0000000..f3d9211 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:13:44.324380.json @@ -0,0 +1 @@ +{"start_time":4487319,"end_time":19324827,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":86598000},"update_repo":{"secs":3,"nanos":813285000},"fetch_signature":{"secs":0,"nanos":25621000},"fetch_binary":{"secs":3,"nanos":278512000},"verify_binary":{"secs":2,"nanos":915924000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:14:40.746971.json b/benchmark-output/performance-benchmark-2024-08-20 09:14:40.746971.json new file mode 100644 index 0000000..a09cf26 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:14:40.746971.json @@ -0,0 +1 @@ +{"start_time":4411016,"end_time":19169330,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84311000},"update_repo":{"secs":3,"nanos":816114000},"fetch_signature":{"secs":0,"nanos":23572000},"fetch_binary":{"secs":3,"nanos":205915000},"verify_binary":{"secs":2,"nanos":915867000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:15:38.176186.json b/benchmark-output/performance-benchmark-2024-08-20 09:15:38.176186.json new file mode 100644 index 0000000..b5fdfcd --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:15:38.176186.json @@ -0,0 +1 @@ +{"start_time":4387653,"end_time":20192288,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":112985000},"update_repo":{"secs":4,"nanos":807699000},"fetch_signature":{"secs":0,"nanos":24339000},"fetch_binary":{"secs":3,"nanos":237070000},"verify_binary":{"secs":2,"nanos":915911000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:16:35.334034.json b/benchmark-output/performance-benchmark-2024-08-20 09:16:35.334034.json new file mode 100644 index 0000000..62585e7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:16:35.334034.json @@ -0,0 +1 @@ +{"start_time":4423387,"end_time":19917375,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":85401000},"update_repo":{"secs":3,"nanos":815220000},"fetch_signature":{"secs":0,"nanos":24936000},"fetch_binary":{"secs":3,"nanos":943269000},"verify_binary":{"secs":2,"nanos":915917000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:17:31.844067.json b/benchmark-output/performance-benchmark-2024-08-20 09:17:31.844067.json new file mode 100644 index 0000000..dbd9f08 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:17:31.844067.json @@ -0,0 +1 @@ +{"start_time":4435522,"end_time":19223538,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":57938000},"update_repo":{"secs":3,"nanos":816144000},"fetch_signature":{"secs":0,"nanos":29394000},"fetch_binary":{"secs":3,"nanos":266374000},"verify_binary":{"secs":2,"nanos":915978000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:18:28.999147.json b/benchmark-output/performance-benchmark-2024-08-20 09:18:28.999147.json new file mode 100644 index 0000000..8ea29c1 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:18:28.999147.json @@ -0,0 +1 @@ +{"start_time":4401214,"end_time":19871597,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":86124000},"update_repo":{"secs":3,"nanos":814132000},"fetch_signature":{"secs":0,"nanos":24313000},"fetch_binary":{"secs":3,"nanos":906348000},"verify_binary":{"secs":2,"nanos":915973000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:19:25.625981.json b/benchmark-output/performance-benchmark-2024-08-20 09:19:25.625981.json new file mode 100644 index 0000000..d08f4c1 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:19:25.625981.json @@ -0,0 +1 @@ +{"start_time":4486487,"end_time":19307568,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":89765000},"update_repo":{"secs":3,"nanos":814650000},"fetch_signature":{"secs":0,"nanos":26758000},"fetch_binary":{"secs":3,"nanos":270137000},"verify_binary":{"secs":2,"nanos":915969000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:20:31.062208.json b/benchmark-output/performance-benchmark-2024-08-20 09:20:31.062208.json new file mode 100644 index 0000000..8c578cd --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:20:31.062208.json @@ -0,0 +1 @@ +{"start_time":4435727,"end_time":18603643,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":90344000},"update_repo":{"secs":3,"nanos":203152000},"fetch_signature":{"secs":0,"nanos":27243000},"fetch_binary":{"secs":3,"nanos":221473000},"verify_binary":{"secs":2,"nanos":915992000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:21:28.611003.json b/benchmark-output/performance-benchmark-2024-08-20 09:21:28.611003.json new file mode 100644 index 0000000..476fef7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:21:28.611003.json @@ -0,0 +1 @@ +{"start_time":4383904,"end_time":20220382,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84264000},"update_repo":{"secs":4,"nanos":839955000},"fetch_signature":{"secs":0,"nanos":25870000},"fetch_binary":{"secs":3,"nanos":263669000},"verify_binary":{"secs":2,"nanos":915902000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:22:25.340802.json b/benchmark-output/performance-benchmark-2024-08-20 09:22:25.340802.json new file mode 100644 index 0000000..e5dd2c7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:22:25.340802.json @@ -0,0 +1 @@ +{"start_time":4472209,"end_time":19390332,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":103850000},"update_repo":{"secs":3,"nanos":816403000},"fetch_signature":{"secs":0,"nanos":24788000},"fetch_binary":{"secs":3,"nanos":331809000},"verify_binary":{"secs":2,"nanos":915890000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:23:22.684988.json b/benchmark-output/performance-benchmark-2024-08-20 09:23:22.684988.json new file mode 100644 index 0000000..0d78126 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:23:22.684988.json @@ -0,0 +1 @@ +{"start_time":4476887,"end_time":19996699,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84096000},"update_repo":{"secs":3,"nanos":815918000},"fetch_signature":{"secs":0,"nanos":23659000},"fetch_binary":{"secs":3,"nanos":972297000},"verify_binary":{"secs":2,"nanos":915938000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:24:20.949997.json b/benchmark-output/performance-benchmark-2024-08-20 09:24:20.949997.json new file mode 100644 index 0000000..a5af356 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:24:20.949997.json @@ -0,0 +1 @@ +{"start_time":4386629,"end_time":20955039,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":54464000},"update_repo":{"secs":4,"nanos":840350000},"fetch_signature":{"secs":0,"nanos":28061000},"fetch_binary":{"secs":3,"nanos":996547000},"verify_binary":{"secs":2,"nanos":915964000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:25:17.577855.json b/benchmark-output/performance-benchmark-2024-08-20 09:25:17.577855.json new file mode 100644 index 0000000..f0121c4 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:25:17.577855.json @@ -0,0 +1 @@ +{"start_time":4467581,"end_time":19285607,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":87068000},"update_repo":{"secs":3,"nanos":816078000},"fetch_signature":{"secs":0,"nanos":24224000},"fetch_binary":{"secs":3,"nanos":264436000},"verify_binary":{"secs":2,"nanos":915924000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:26:15.844016.json b/benchmark-output/performance-benchmark-2024-08-20 09:26:15.844016.json new file mode 100644 index 0000000..1061d17 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:26:15.844016.json @@ -0,0 +1 @@ +{"start_time":4398752,"end_time":20949998,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":87163000},"update_repo":{"secs":4,"nanos":839550000},"fetch_signature":{"secs":0,"nanos":24132000},"fetch_binary":{"secs":3,"nanos":971643000},"verify_binary":{"secs":2,"nanos":915937000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:27:12.474049.json b/benchmark-output/performance-benchmark-2024-08-20 09:27:12.474049.json new file mode 100644 index 0000000..6c8f781 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:27:12.474049.json @@ -0,0 +1 @@ +{"start_time":4431829,"end_time":19250524,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":87162000},"update_repo":{"secs":3,"nanos":817357000},"fetch_signature":{"secs":0,"nanos":24167000},"fetch_binary":{"secs":3,"nanos":259660000},"verify_binary":{"secs":2,"nanos":915902000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:28:09.714267.json b/benchmark-output/performance-benchmark-2024-08-20 09:28:09.714267.json new file mode 100644 index 0000000..1936363 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:28:09.714267.json @@ -0,0 +1 @@ +{"start_time":4430618,"end_time":19937473,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":83228000},"update_repo":{"secs":3,"nanos":818778000},"fetch_signature":{"secs":0,"nanos":22357000},"fetch_binary":{"secs":3,"nanos":948933000},"verify_binary":{"secs":2,"nanos":915867000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:29:06.342918.json b/benchmark-output/performance-benchmark-2024-08-20 09:29:06.342918.json new file mode 100644 index 0000000..3823cb2 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:29:06.342918.json @@ -0,0 +1 @@ +{"start_time":4431608,"end_time":19249290,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":87085000},"update_repo":{"secs":3,"nanos":817215000},"fetch_signature":{"secs":0,"nanos":23266000},"fetch_binary":{"secs":3,"nanos":255446000},"verify_binary":{"secs":2,"nanos":915967000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:30:02.885976.json b/benchmark-output/performance-benchmark-2024-08-20 09:30:02.885976.json new file mode 100644 index 0000000..d36b99b --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:30:02.885976.json @@ -0,0 +1 @@ +{"start_time":4460917,"end_time":19283019,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":87644000},"update_repo":{"secs":3,"nanos":817021000},"fetch_signature":{"secs":0,"nanos":24469000},"fetch_binary":{"secs":3,"nanos":266397000},"verify_binary":{"secs":2,"nanos":916010000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:31:00.110025.json b/benchmark-output/performance-benchmark-2024-08-20 09:31:00.110025.json new file mode 100644 index 0000000..18bbf05 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:31:00.110025.json @@ -0,0 +1 @@ +{"start_time":4431146,"end_time":19942781,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":85944000},"update_repo":{"secs":3,"nanos":826364000},"fetch_signature":{"secs":0,"nanos":23593000},"fetch_binary":{"secs":3,"nanos":931214000},"verify_binary":{"secs":2,"nanos":915923000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:31:57.370331.json b/benchmark-output/performance-benchmark-2024-08-20 09:31:57.370331.json new file mode 100644 index 0000000..2a72c98 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:31:57.370331.json @@ -0,0 +1 @@ +{"start_time":4411839,"end_time":19950582,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":54961000},"update_repo":{"secs":3,"nanos":817694000},"fetch_signature":{"secs":0,"nanos":25027000},"fetch_binary":{"secs":3,"nanos":995983000},"verify_binary":{"secs":2,"nanos":915962000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:32:55.619965.json b/benchmark-output/performance-benchmark-2024-08-20 09:32:55.619965.json new file mode 100644 index 0000000..2f4998d --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:32:55.619965.json @@ -0,0 +1 @@ +{"start_time":4387125,"end_time":20826910,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":82664000},"update_repo":{"secs":4,"nanos":843015000},"fetch_signature":{"secs":0,"nanos":24982000},"fetch_binary":{"secs":3,"nanos":842188000},"verify_binary":{"secs":2,"nanos":915917000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:33:52.964818.json b/benchmark-output/performance-benchmark-2024-08-20 09:33:52.964818.json new file mode 100644 index 0000000..e0edd91 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:33:52.964818.json @@ -0,0 +1 @@ +{"start_time":4443362,"end_time":19986137,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":85551000},"update_repo":{"secs":3,"nanos":818862000},"fetch_signature":{"secs":0,"nanos":23523000},"fetch_binary":{"secs":3,"nanos":990664000},"verify_binary":{"secs":2,"nanos":915935000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:34:50.308819.json b/benchmark-output/performance-benchmark-2024-08-20 09:34:50.308819.json new file mode 100644 index 0000000..e9b27f2 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:34:50.308819.json @@ -0,0 +1 @@ +{"start_time":4449041,"end_time":19982841,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":52326000},"update_repo":{"secs":3,"nanos":818567000},"fetch_signature":{"secs":0,"nanos":27571000},"fetch_binary":{"secs":4,"nanos":1449000},"verify_binary":{"secs":2,"nanos":915851000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:35:47.567040.json b/benchmark-output/performance-benchmark-2024-08-20 09:35:47.567040.json new file mode 100644 index 0000000..ac91cc8 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:35:47.567040.json @@ -0,0 +1 @@ +{"start_time":4456692,"end_time":19992155,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":116741000},"update_repo":{"secs":3,"nanos":787665000},"fetch_signature":{"secs":0,"nanos":23320000},"fetch_binary":{"secs":3,"nanos":970644000},"verify_binary":{"secs":2,"nanos":915952000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:36:44.081613.json b/benchmark-output/performance-benchmark-2024-08-20 09:36:44.081613.json new file mode 100644 index 0000000..822a15c --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:36:44.081613.json @@ -0,0 +1 @@ +{"start_time":4439824,"end_time":19214927,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":88103000},"update_repo":{"secs":3,"nanos":818913000},"fetch_signature":{"secs":0,"nanos":25059000},"fetch_binary":{"secs":3,"nanos":214162000},"verify_binary":{"secs":2,"nanos":915925000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:37:41.421666.json b/benchmark-output/performance-benchmark-2024-08-20 09:37:41.421666.json new file mode 100644 index 0000000..1b3f1e7 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:37:41.421666.json @@ -0,0 +1 @@ +{"start_time":4461964,"end_time":19987855,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":60683000},"update_repo":{"secs":3,"nanos":819785000},"fetch_signature":{"secs":0,"nanos":28545000},"fetch_binary":{"secs":3,"nanos":981710000},"verify_binary":{"secs":2,"nanos":916025000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:38:38.049922.json b/benchmark-output/performance-benchmark-2024-08-20 09:38:38.049922.json new file mode 100644 index 0000000..6e0429c --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:38:38.049922.json @@ -0,0 +1 @@ +{"start_time":4441771,"end_time":19259790,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":83900000},"update_repo":{"secs":3,"nanos":819932000},"fetch_signature":{"secs":0,"nanos":32941000},"fetch_binary":{"secs":3,"nanos":258371000},"verify_binary":{"secs":2,"nanos":915913000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:39:35.366876.json b/benchmark-output/performance-benchmark-2024-08-20 09:39:35.366876.json new file mode 100644 index 0000000..b6ec72e --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:39:35.366876.json @@ -0,0 +1 @@ +{"start_time":4414667,"end_time":19952507,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":89609000},"update_repo":{"secs":3,"nanos":820839000},"fetch_signature":{"secs":0,"nanos":24303000},"fetch_binary":{"secs":3,"nanos":967507000},"verify_binary":{"secs":2,"nanos":915860000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:40:32.950956.json b/benchmark-output/performance-benchmark-2024-08-20 09:40:32.950956.json new file mode 100644 index 0000000..1dde11a --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:40:32.950956.json @@ -0,0 +1 @@ +{"start_time":4402665,"end_time":20263594,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":84695000},"update_repo":{"secs":4,"nanos":844120000},"fetch_signature":{"secs":0,"nanos":23613000},"fetch_binary":{"secs":3,"nanos":249129000},"verify_binary":{"secs":2,"nanos":915911000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:41:29.679246.json b/benchmark-output/performance-benchmark-2024-08-20 09:41:29.679246.json new file mode 100644 index 0000000..abf1a0c --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:41:29.679246.json @@ -0,0 +1 @@ +{"start_time":4431043,"end_time":19359882,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":85793000},"update_repo":{"secs":3,"nanos":820469000},"fetch_signature":{"secs":0,"nanos":23406000},"fetch_binary":{"secs":3,"nanos":341897000},"verify_binary":{"secs":2,"nanos":915901000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:42:27.020558.json b/benchmark-output/performance-benchmark-2024-08-20 09:42:27.020558.json new file mode 100644 index 0000000..3362bad --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:42:27.020558.json @@ -0,0 +1 @@ +{"start_time":4407016,"end_time":19958730,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":147024000},"update_repo":{"secs":3,"nanos":790307000},"fetch_signature":{"secs":0,"nanos":23777000},"fetch_binary":{"secs":3,"nanos":940908000},"verify_binary":{"secs":2,"nanos":915879000}} \ No newline at end of file diff --git a/benchmark-output/performance-benchmark-2024-08-20 09:43:24.365362.json b/benchmark-output/performance-benchmark-2024-08-20 09:43:24.365362.json new file mode 100644 index 0000000..bf24a06 --- /dev/null +++ b/benchmark-output/performance-benchmark-2024-08-20 09:43:24.365362.json @@ -0,0 +1 @@ +{"start_time":4439164,"end_time":20007720,"binary_size":434668,"fetch_target_files":{"secs":0,"nanos":87568000},"update_repo":{"secs":3,"nanos":820602000},"fetch_signature":{"secs":0,"nanos":24415000},"fetch_binary":{"secs":3,"nanos":977224000},"verify_binary":{"secs":2,"nanos":915946000}} \ No newline at end of file diff --git a/benchmarking/.gitignore b/benchmarking/.gitignore new file mode 100644 index 0000000..addc655 --- /dev/null +++ b/benchmarking/.gitignore @@ -0,0 +1,3 @@ +__pychache__ +plots +benchmark-output \ No newline at end of file diff --git a/benchmarking/benchmark_server.py b/benchmarking/benchmark_server.py new file mode 100644 index 0000000..ae50a58 --- /dev/null +++ b/benchmarking/benchmark_server.py @@ -0,0 +1,240 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pydantic import BaseModel +from fastapi import FastAPI +import uvicorn +from datetime import datetime +import os + +class BenchmarkData(BaseModel): + start_time: int + end_time: int + binary_size: int | None + fetch_target_files: dict + update_repo: dict + fetch_signature: dict + fetch_binary: dict + verify_binary: dict + verify_binary_naive: dict | None + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + +app = FastAPI() + +@app.post("/") +async def data(data: BenchmarkData): + os.makedirs("benchmark-output", exist_ok=True) + with open(f"benchmark-output/performance-benchmark-{datetime.utcnow()}.json", "w+") as f: + f.write(data.json()) + print(f"{data}") + return {} + +if __name__ == "__main__": + if __name__ == "__main__": + uvicorn.run("benchmark_server:app", port=8080, host="0.0.0.0", log_level="info") \ No newline at end of file diff --git a/benchmarking/performance-benchmarks.ipynb b/benchmarking/performance-benchmarks.ipynb new file mode 100644 index 0000000..2cabfd6 --- /dev/null +++ b/benchmarking/performance-benchmarks.ipynb @@ -0,0 +1,899 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 442, + "metadata": {}, + "outputs": [], + "source": [ + "#\"\"\n", + "# Apache License\n", + "# Version 2.0, January 2004\n", + "# http://www.apache.org/licenses/\n", + "#\n", + "# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n", + "#\n", + "# 1. Definitions.\n", + "#\n", + "# \"License\" shall mean the terms and conditions for use, reproduction,\n", + "# and distribution as defined by Sections 1 through 9 of this document.\n", + "#\n", + "# \"Licensor\" shall mean the copyright owner or entity authorized by\n", + "# the copyright owner that is granting the License.\n", + "#\n", + "# \"Legal Entity\" shall mean the union of the acting entity and all\n", + "# other entities that control, are controlled by, or are under common\n", + "# control with that entity. For the purposes of this definition,\n", + "# \"control\" means (i) the power, direct or indirect, to cause the\n", + "# direction or management of such entity, whether by contract or\n", + "# otherwise, or (ii) ownership of fifty percent (50%) or more of the\n", + "# outstanding shares, or (iii) beneficial ownership of such entity.\n", + "#\n", + "# \"You\" (or \"Your\") shall mean an individual or Legal Entity\n", + "# exercising permissions granted by this License.\n", + "#\n", + "# \"Source\" form shall mean the preferred form for making modifications,\n", + "# including but not limited to software source code, documentation\n", + "# source, and configuration files.\n", + "#\n", + "# \"Object\" form shall mean any form resulting from mechanical\n", + "# transformation or translation of a Source form, including but\n", + "# not limited to compiled object code, generated documentation,\n", + "# and conversions to other media types.\n", + "#\n", + "# \"Work\" shall mean the work of authorship, whether in Source or\n", + "# Object form, made available under the License, as indicated by a\n", + "# copyright notice that is included in or attached to the work\n", + "# (an example is provided in the Appendix below).\n", + "#\n", + "# \"Derivative Works\" shall mean any work, whether in Source or Object\n", + "# form, that is based on (or derived from) the Work and for which the\n", + "# editorial revisions, annotations, elaborations, or other modifications\n", + "# represent, as a whole, an original work of authorship. For the purposes\n", + "# of this License, Derivative Works shall not include works that remain\n", + "# separable from, or merely link (or bind by name) to the interfaces of,\n", + "# the Work and Derivative Works thereof.\n", + "#\n", + "# \"Contribution\" shall mean any work of authorship, including\n", + "# the original version of the Work and any modifications or additions\n", + "# to that Work or Derivative Works thereof, that is intentionally\n", + "# submitted to Licensor for inclusion in the Work by the copyright owner\n", + "# or by an individual or Legal Entity authorized to submit on behalf of\n", + "# the copyright owner. For the purposes of this definition, \"submitted\"\n", + "# means any form of electronic, verbal, or written communication sent\n", + "# to the Licensor or its representatives, including but not limited to\n", + "# communication on electronic mailing lists, source code control systems,\n", + "# and issue tracking systems that are managed by, or on behalf of, the\n", + "# Licensor for the purpose of discussing and improving the Work, but\n", + "# excluding communication that is conspicuously marked or otherwise\n", + "# designated in writing by the copyright owner as \"Not a Contribution.\"\n", + "#\n", + "# \"Contributor\" shall mean Licensor and any individual or Legal Entity\n", + "# on behalf of whom a Contribution has been received by Licensor and\n", + "# subsequently incorporated within the Work.\n", + "#\n", + "# 2. Grant of Copyright License. Subject to the terms and conditions of\n", + "# this License, each Contributor hereby grants to You a perpetual,\n", + "# worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n", + "# copyright license to reproduce, prepare Derivative Works of,\n", + "# publicly display, publicly perform, sublicense, and distribute the\n", + "# Work and such Derivative Works in Source or Object form.\n", + "#\n", + "# 3. Grant of Patent License. Subject to the terms and conditions of\n", + "# this License, each Contributor hereby grants to You a perpetual,\n", + "# worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n", + "# (except as stated in this section) patent license to make, have made,\n", + "# use, offer to sell, sell, import, and otherwise transfer the Work,\n", + "# where such license applies only to those patent claims licensable\n", + "# by such Contributor that are necessarily infringed by their\n", + "# Contribution(s) alone or by combination of their Contribution(s)\n", + "# with the Work to which such Contribution(s) was submitted. If You\n", + "# institute patent litigation against any entity (including a\n", + "# cross-claim or counterclaim in a lawsuit) alleging that the Work\n", + "# or a Contribution incorporated within the Work constitutes direct\n", + "# or contributory patent infringement, then any patent licenses\n", + "# granted to You under this License for that Work shall terminate\n", + "# as of the date such litigation is filed.\n", + "#\n", + "# 4. Redistribution. You may reproduce and distribute copies of the\n", + "# Work or Derivative Works thereof in any medium, with or without\n", + "# modifications, and in Source or Object form, provided that You\n", + "# meet the following conditions:\n", + "#\n", + "# (a) You must give any other recipients of the Work or\n", + "# Derivative Works a copy of this License; and\n", + "#\n", + "# (b) You must cause any modified files to carry prominent notices\n", + "# stating that You changed the files; and\n", + "#\n", + "# (c) You must retain, in the Source form of any Derivative Works\n", + "# that You distribute, all copyright, patent, trademark, and\n", + "# attribution notices from the Source form of the Work,\n", + "# excluding those notices that do not pertain to any part of\n", + "# the Derivative Works; and\n", + "#\n", + "# (d) If the Work includes a \"NOTICE\" text file as part of its\n", + "# distribution, then any Derivative Works that You distribute must\n", + "# include a readable copy of the attribution notices contained\n", + "# within such NOTICE file, excluding those notices that do not\n", + "# pertain to any part of the Derivative Works, in at least one\n", + "# of the following places: within a NOTICE text file distributed\n", + "# as part of the Derivative Works; within the Source form or\n", + "# documentation, if provided along with the Derivative Works; or,\n", + "# within a display generated by the Derivative Works, if and\n", + "# wherever such third-party notices normally appear. The contents\n", + "# of the NOTICE file are for informational purposes only and\n", + "# do not modify the License. You may add Your own attribution\n", + "# notices within Derivative Works that You distribute, alongside\n", + "# or as an addendum to the NOTICE text from the Work, provided\n", + "# that such additional attribution notices cannot be construed\n", + "# as modifying the License.\n", + "#\n", + "# You may add Your own copyright statement to Your modifications and\n", + "# may provide additional or different license terms and conditions\n", + "# for use, reproduction, or distribution of Your modifications, or\n", + "# for any such Derivative Works as a whole, provided Your use,\n", + "# reproduction, and distribution of the Work otherwise complies with\n", + "# the conditions stated in this License.\n", + "#\n", + "# 5. Submission of Contributions. Unless You explicitly state otherwise,\n", + "# any Contribution intentionally submitted for inclusion in the Work\n", + "# by You to the Licensor shall be under the terms and conditions of\n", + "# this License, without any additional terms or conditions.\n", + "# Notwithstanding the above, nothing herein shall supersede or modify\n", + "# the terms of any separate license agreement you may have executed\n", + "# with Licensor regarding such Contributions.\n", + "#\n", + "# 6. Trademarks. This License does not grant permission to use the trade\n", + "# names, trademarks, service marks, or product names of the Licensor,\n", + "# except as required for reasonable and customary use in describing the\n", + "# origin of the Work and reproducing the content of the NOTICE file.\n", + "#\n", + "# 7. Disclaimer of Warranty. Unless required by applicable law or\n", + "# agreed to in writing, Licensor provides the Work (and each\n", + "# Contributor provides its Contributions) on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n", + "# implied, including, without limitation, any warranties or conditions\n", + "# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n", + "# PARTICULAR PURPOSE. You are solely responsible for determining the\n", + "# appropriateness of using or redistributing the Work and assume any\n", + "# risks associated with Your exercise of permissions under this License.\n", + "#\n", + "# 8. Limitation of Liability. In no event and under no legal theory,\n", + "# whether in tort (including negligence), contract, or otherwise,\n", + "# unless required by applicable law (such as deliberate and grossly\n", + "# negligent acts) or agreed to in writing, shall any Contributor be\n", + "# liable to You for damages, including any direct, indirect, special,\n", + "# incidental, or consequential damages of any character arising as a\n", + "# result of this License or out of the use or inability to use the\n", + "# Work (including but not limited to damages for loss of goodwill,\n", + "# work stoppage, computer failure or malfunction, or any and all\n", + "# other commercial damages or losses), even if such Contributor\n", + "# has been advised of the possibility of such damages.\n", + "#\n", + "# 9. Accepting Warranty or Additional Liability. While redistributing\n", + "# the Work or Derivative Works thereof, You may choose to offer,\n", + "# and charge a fee for, acceptance of support, warranty, indemnity,\n", + "# or other liability obligations and/or rights consistent with this\n", + "# License. However, in accepting such obligations, You may act only\n", + "# on Your own behalf and on Your sole responsibility, not on behalf\n", + "# of any other Contributor, and only if You agree to indemnify,\n", + "# defend, and hold each Contributor harmless for any liability\n", + "# incurred by, or claims asserted against, such Contributor by reason\n", + "# of your accepting any such warranty or additional liability.\n", + "#\n", + "# END OF TERMS AND CONDITIONS\n", + "#\n", + "# APPENDIX: How to apply the Apache License to your work.\n", + "#\n", + "# To apply the Apache License to your work, attach the following\n", + "# boilerplate notice, with the fields enclosed by brackets \"[]\"\n", + "# replaced with your own identifying information. (Don't include\n", + "# the brackets!) The text should be enclosed in the appropriate\n", + "# comment syntax for the file format. We also recommend that a\n", + "# file or class name and description of purpose be included on the\n", + "# same \"printed page\" as the copyright notice for easier\n", + "# identification within third-party archives.\n", + "#\n", + "# Copyright 2024 Fraunhofer AISEC\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import datetime\n", + "from matplotlib.patches import Polygon\n", + "import os\n", + "from datetime import timedelta\n", + "from itertools import product\n", + "from functools import reduce\n", + "from typing import Iterable\n", + "from mpl_toolkits.axes_grid1 import host_subplot\n", + "from pydantic import BaseModel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Helpers and Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 443, + "metadata": {}, + "outputs": [], + "source": [ + "def dict_to_seconds(d: dict) -> float:\n", + " return timedelta(seconds=d[\"secs\"] + 1e-9*d[\"nanos\"]).total_seconds()\n", + "\n", + "def helper(acc: dict, a):\n", + " if a.binary_size not in acc:\n", + " acc[a.binary_size] = []\n", + " acc[a.binary_size].append(a)\n", + " return acc\n", + "\n", + "os.makedirs(\"plots\", exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Styling" + ] + }, + { + "cell_type": "code", + "execution_count": 444, + "metadata": {}, + "outputs": [], + "source": [ + "plt.style.use(\"Solarize_Light2\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notes on Data\n", + "\n", + "- The data was collected in full runs of the application. On a Pi Pico W connected to a WiFi network that had other devices contending for the Network, however it is unlikely that the network bandwidth was a major bottlenecks.\n", + "- It being collected in full runs means there was no \"heating up\" with caches being filled. This is reflective of the expected operation which is unlikely to be in a hot loop. Furthermore, the processor pipeline of the RP2040 processor is simple enough (no speculative execution, etc.) that we would likey see no major differences.\n", + "- To simulate different binary sizes we padded the update binary with random bytes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prepare Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 445, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{41440: [1.067313, 0.342537, 1.132877, 1.032674, 1.03299, 1.061199, 1.138777, 1.075226, 0.335845, 0.439961], 106992: [1.563285, 0.882224, 0.86273, 0.819779, 1.591189, 0.870011, 0.871892, 0.802845, 0.874702, 1.614286, 0.917692], 172520: [2.053178, 1.340138, 2.037076, 1.35745, 2.069017, 1.254208, 2.001739, 1.290192, 1.267674, 2.088228, 2.018034, 1.997783, 2.057031, 2.066434], 238060: [2.531167, 1.816302, 1.833615, 1.800608, 2.534603, 1.83235, 2.439917, 1.8789, 2.560813, 1.804081, 2.523504, 1.799234, 1.805855, 2.488641, 2.532835, 1.787092], 303592: [3.019667, 2.331732, 2.98431, 3.01598, 2.350826, 2.369321, 2.325375, 3.040432, 2.971757], 369132: [2.844935, 3.503253, 3.440748, 2.849283, 2.75979, 2.863839, 2.767413, 3.432831, 2.777237, 3.448716, 3.492996], 434668: [3.214162, 3.972297, 3.278512, 3.205915, 3.906348, 3.258371, 3.266374, 3.971643, 3.25966, 3.952907, 3.977224, 3.943269, 3.970644, 3.331809, 3.221473, 3.967507, 3.264436, 3.341897, 3.948933, 3.995983, 3.23707, 3.263669, 3.931214, 3.98171, 3.249129, 3.996547, 3.842188, 3.990664, 3.940908, 4.001449, 3.270137, 3.266397, 3.255446]}\n" + ] + } + ], + "source": [ + "class BenchmarkData(BaseModel):\n", + " start_time: int\n", + " end_time: int\n", + " binary_size: int | None\n", + " fetch_target_files: dict\n", + " update_repo: dict\n", + " fetch_signature: dict\n", + " fetch_binary: dict\n", + " verify_binary: dict\n", + " \n", + "files = [f for f in os.listdir(\"../benchmark-output\") if f.startswith(\"performance-benchmark\")]\n", + "data = [BenchmarkData.parse_file(f\"../benchmark-output/{f}\") for f in files]\n", + "data.sort(key=lambda x: x.binary_size)\n", + "data_fetch_targets = [m.fetch_target_files for m in data]\n", + "data_update_repo = [m.update_repo for m in data]\n", + "data_fetch_signature = [m.fetch_signature for m in data]\n", + "data_fetch_binary = [m.fetch_binary for m in data]\n", + "\n", + "data_by_size = reduce(helper, data, {})\n", + "data_fetch_binary_by_size = {k:[d.fetch_binary for d in vals] for (k,vals) in data_by_size.items()}\n", + "data_fetch_binary_by_size = {k: [dict_to_seconds(d) for d in vals] for (k,vals) in data_fetch_binary_by_size.items()}\n", + "print(data_fetch_binary_by_size)\n", + "data_verify_binary = [m.verify_binary for m in data]\n", + "data_fetch_targets = [dict_to_seconds(d) for d in data_fetch_targets]\n", + "data_update_repo = [dict_to_seconds(d) for d in data_update_repo]\n", + "data_fetch_signature = [dict_to_seconds(d) for d in data_fetch_signature]\n", + "data_fetch_binary = [dict_to_seconds(d) for d in data_fetch_binary]\n", + "data_verify_binary_full = [dict_to_seconds(d) for d in data_verify_binary]\n", + "data_verify_binary = {k:[d.verify_binary for d in vals] for (k,vals) in data_by_size.items()}\n", + "data_verify_binary = {k: [dict_to_seconds(d) for d in vals] for (k,vals) in data_verify_binary.items()}\n", + "\n", + "\n", + "data_full = {k:[\n", + " (timedelta(seconds=d.fetch_target_files[\"secs\"] + d.fetch_target_files[\"nanos\"] * 1e-9)\n", + " + timedelta(seconds=d.update_repo[\"secs\"] + d.update_repo[\"nanos\"] * 1e-9)\n", + " + timedelta(seconds=d.fetch_signature[\"secs\"] + d.fetch_signature[\"nanos\"] * 1e-9)\n", + " + timedelta(seconds=d.fetch_binary[\"secs\"] + d.fetch_binary[\"nanos\"] * 1e-9)\n", + " + timedelta(seconds=d.verify_binary[\"secs\"] + d.verify_binary[\"nanos\"] * 1e-9)\n", + " ).total_seconds()\n", + " for d in vals\n", + "] for (k,vals) in data_by_size.items()}\n", + "data_no_tuf = {k:[\n", + " (timedelta(seconds=d.fetch_signature[\"secs\"] + d.fetch_signature[\"nanos\"] * 1e-9)\n", + " + timedelta(seconds=d.fetch_binary[\"secs\"] + d.fetch_binary[\"nanos\"] * 1e-9)\n", + " + timedelta(seconds=d.verify_binary[\"secs\"] + d.verify_binary[\"nanos\"] * 1e-9)\n", + " ).total_seconds()\n", + " for d in vals\n", + "] for (k,vals) in data_by_size.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": 446, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig: plt.Figure\n", + "axs: Iterable[plt.Axes]\n", + "fig, ax = plt.subplots(1, 1)\n", + "\n", + "ax.boxplot(\n", + " data_fetch_targets,\n", + " tick_labels=[\"\"],\n", + " )\n", + "#ax.set_title(\"Fetch TUF Target Files\")\n", + "ax.set_ylabel(\"Time (s)\")\n", + "fig.subplots_adjust(left=0.1, right=0.5, bottom=0.05, top=0.5, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-fetch-tuf-targets.pdf\", bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": 447, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig: plt.Figure\n", + "axs: Iterable[plt.Axes]\n", + "fig, ax = plt.subplots(1, 1)\n", + "ax.boxplot(\n", + " data_update_repo,\n", + " tick_labels=[\"\"],\n", + " )\n", + "# ax.set_title(\"TUF Repo Update\")\n", + "ax.set_ylabel(\"Time (s)\")\n", + "fig.subplots_adjust(left=0.1, right=0.5, bottom=0.05, top=0.5, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-tuf-repo-update.pdf\", bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": 448, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAO8AAACUCAYAAAB7lX67AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAAsTAAALEwEAmpwYAAAQFElEQVR4nO3de1STd54G8OdNuAYSEm6CIHgD8YbXqkWodqzWWq1oZ23ruGtnO7Vn2q529LQ7056x53Q6vc24p+u2zh53R3s5XWe1DouVqoO1tKgVEbywVAUFFLnINRDuIcn+gWSNEngJvAlv8nzO6Tny8ibv9039+t5+vyeCqa3cAiKSHYWrCyAix7B5iWSKzUskU2xeIpli8xLJFJuXSKa8XF2AszU1N7u6BLckCAIsFj51lIIAQKPR3LecR14aFhq12tUluC2Fou82ZfMSyRSbl4bkYFoGkhathndgLJIWrcbBtAxXl+QxPO6al4bPwbQMvP3uTuz8l7fw6NLFOJaZhc1btwMAnlzzuIurc3+Cp41t5g2r4ZO0aDXe//3rSEmejyCNBk3Nzcg+mYN/fuMdnP4u3dXluQ2lQoHAwMD7lvO0mRxWVFyCBfNn2yxbMH82iopLXFSRZ2HzksPi48bjTE6+zbIzOfmIjxvvooo8C5uXHLbtlU3YvHU7sk/mwGg0IvtkDjZv3Y5tr2xydWkegde8NCQH0zKw48PdKCouQXzceGx7ZRNvVg0ze9e8bF4aFr03rGj48YYVkZth8xLJlNMGaRRev4X9fzsLs8WChTPjsDwp0eb3xm4TPjmUjZvV9Qjw98Uv1ixCqFaN0opafPH1aQCABcDKlJmYlRCLhuZWfHIoG82t7RAgIHlWPJbMm+Ks3SFyOac0r9lsxr6jOdiyfhl0GhXe3XMYiXExGB2mta5z6kIxVH4++N2LTyK3sARpJ/Lw/NrFiArX4TfPrYJSoUCToQ1v/+chJMaPgVIQ8NMlDyAmMgQdnUa8s+crTB432uY9idyZU06byyrrEB6sRphODS+lEg9MGYdLRTdt1rlUfBMPJk4EAMyePBZXyqpgsVjg4+0F5Z1ZFUaTqWd+FIAgtQoxkSEAAD9fb0SEBEFvaHPG7hCNCE458jYa2qBTB1h/1moCUFpRa7OO3tAGnaZnHaVCAX9fH7S2dyJQ5YfSilp8dvgUGppa8OwTKdZm7lWnN6D8dgPGRYX2uf3s/KvIPl8EAHh25YMYParv9WhogvqYc0pD19LS0udyWUxMGBcVhjdfSEVVnR6fHDqJaROj4O3VU3pHlxG7D2Zh3dJ58Pf16fP1KbMnIWX2JAA9j4r4SGP48VGRdO49WPVyymmzTq1Co6HV+rO+uRU6tcpmHa1ahcbmnnVMZjPaO7sQ4O9rs05kqBZ+Pl6orNH3rGcyY/fBbzFv2njMSoiVdieIRhinNG/s6FDUNDSjTm9At8mE3B9LkRg/xmadxLgx+OHSNQBA/uUyTBobCUEQUKc3wGQ2AwDqm1pQXd+EEG0gLBYLPss4hYiQIDwyf6ozdoNoRHHaCKuCa7dwIPMszGYLkmZMxIrkGTj03XnERoZgRnwMjN3d2JuejfLbDVD59TwqCtOpcabgOo6dLoBSIUAQBDyeMgMzJ8XiWvlt/PGzI4gK1/Xew8Lqh+dg+sTofuvgqZ00eNosHQ6PvIN/waTB5pUOh0cSuRk2L5FMsXmJZIrNSyRTbF4imWLz0pAwt9l1ZDE8kkYm5ja7Fp/zksOY2+wcfM5Lw465za7F5iWHMbfZtdi85DDmNrsWr3lpSJjbLD1OTLiDzSsNTkyQjr3mlW16pJj3JHJnsk2PFIAB35PInck2PVLMexK5M9mmR4p5TyJ3JovhkX2lRw4Go1+dg9Gv0nBp9Otg0iN1mgBR6ZFi3rMXo1+lx7vN0nFp9KsU6ZFi3pPInck2PdLeew6ERwdp8MgrHYcHadysqkfBtVu4VdOA9o4u+Pv5IDo8GNMmRCF2tPyuHfkXTBpsXukMepBG4fUKpGflo7PLiLiYCEyIDoefjzc6uoyormvCnvTv4evjjdWLZ2HqhP6zkolo+Nlt3pMXirD+sQcxtp+ja1llHY79UMDm9SDv/eFjfLBj16Be89q2F/HrV1+SqCLPxbHNNCyCI6aiobrQ1WW4pSFNxje0dqCjywigZ6jj6YvF+OHSNZgtHtX3RCOKqOb9eP9x1DT0HLHSs/KReaYQ3+QU4svjuZIWR0T2iWremoZmjBkVDADI+d8S/NPTj+BXG5bjXGGppMURkX2iRlgJgoBukxk1Dc3w9/VBcFAgzBYLOo1GqesjIjtENe+0CVH4j79mobW9E3OnjAMAVNXqobUzHJGIpCeqeTc8vhBnLl2DUqnAgukTAAAt7R1YmTJTytqIqB+imtfbS2kd2N9rUmykJAURkTh2b1gdyDyLppa2fl/c1NKGA5lnh70oIhqY3SPvqJAgvLc3A5GhQYiLicCoEI11eGRNfTOKblajur4JKxYOPBmAiIZfvyOsTCYzLhTdROH1ClTUNKK9swsqPx9EheswbUI0EuPH2J1rOFJxhJU0OMJKOg6lRyqVCsyZPBZzJo8dcgGOpkf+WFKJ//k2D90mE7yUSqxdMhcJY3uut3MLS3Dk1CUIgoCgQH/84+qHEKjyG3KtRHLglMNmb3rky08vxZsvpCK3sBSVtXqbde5Oj1wybwrSTuQBAAJVvnhx3RJs35SKjauSsTc9GwBgMpux/29nsXXDcvz2+dWICg/Gt+cuO2N3iEaEEZ8eGRMRYn2ePDpMC2N3N4zdJsACWGBBp7EbFosFHZ1d0AbyuTN5DlmkR/bKv3IDMREh8PZSAgCeWf4gfrc7HT7eXggP1uCZ5QucsDdEI4Ms0iMBoLK2EWkn8rBl/VIAPTfTvs+/ijd+sQqhWjX+ciwHR08X9BmFw/RI52B6pDSGnB5ZXadH3uUbaG5txzPLF6C6To9ukxnRdyYs9Geo6ZGNza349y+/xbNPJCNM1/MXpPx2AwBYf547ZSyOni7oc/tMj3QOfq7SGFJ6ZN7lMuz4/Cj0hlbkFFwHAHR2dYueEjiU9Mi2jk589N/HsebhOZg4ZpR1fa1ahapaPQytHQCAyyWViAzViqqHyB2IOvJ+9d15bFm/DNGjgpF3uQwAED0qGLdqGkRtRKlQ4KlHF2Dnvkxr0uPoMJ1NeuTCmXHYm56N3+46aE2PBICsc1dQ22hARvYFZGRfAABsXr8MWrUKK1NmYMfnR6BUKhCsCcDGVcmD/wSIZEpU8xraOhAVrrNdKABC7xcHiTB9YjSmT7TNunpi0Szrn729vLDpyYfve92K5Bl2I10fmpOAh+YkiK6ByJ2IOm2OiQixni73OldY2m84HRFJS1QAXXWdHv+6LxOh2kCUVtQiPjYCt+ubsXn9MowKltcdRt5UkQaHR0rH4dD1Xl3GblwqLkdDUyt0GhWmx42Bn4/3sBcqNTavNNi80nFobPPdfLy9rCkaROR6opq3oakFh7Mvory6Hp3GbpvfvfXLtZIURkT9E9W8u/+ahYiQIKxaNAs+d4YmEpFriWre6vomvPbs41AI4h8NEZG0RD0qSowbg+Ib1VLXQkSDIOrI+9Sy+fjg0wyEaTXQBNhOdv8HjmoicglRzfvpVyehEBSICA3iNS/RCCGqea/eqML7m5+Cn6/8nusSuStR17xR4cFoae+QuhYiGgRRI6wOZeXj3OUyJCVOhDrA3+Z3C2fGSVacFDjCamDjEx6EXi/956TValBy5QfJtyN3Qxphde1WDbRqFX4srbRZLkB880qRHtltMuEvR3NQdLMaggCsXjwbsxPGiqqH7NPrmwc91DFIoxn0P4zBEVMHtT7ZEtW8WzcsH9JGetMjt6xfBp1GhXf3HEZiXAxGh2mt69ydHplbWIK0E3l4fu1ia3qkVq1CRU0jdu7LxPtb1gEAjpy8BHWAH9765VqYLRa0tXcOqU4iObF7zWu561vvzRaL3f/EkCQ9EsDpi8VYnjS9Z0cEgZnN5FHsHnl/9cf/woev/gwA8NI7nwL3jq6yWABBwJ9e3zjgRqRIj2zr6DnKHvruPIpuVCNMp8bTjy6AJtD2mpzIXdlt3u0vpFr//PbLP3VGLf26Nz3SbLag0dCG8dHh+Lul83A8pxAHv8nFz1c/dN9rmR45eI4kQTrrNZ5m0OmRwZr/P1LmXS7DsgXT7lvneE4hHpk/8E0HKdIjA/x94ePthVkJsQB6TrVPXSjuc/tMjxy8wX5GjtywcmQ7nmhI6ZFfZ1/se/nJvpffS4r0SEEQkBgXjaI7Y66vlFYiMjRIVD1E7qDfu81XyqoA9NywulpWhbtvT9U1GkQnaUiRHqkJ8Mean8zF3vRsHMg8i0CVHzauXDj4T4BIpvodpPHGR18CABqaW21OowUB0AT449Gk6ZgRHyN9lcOIp2kDcyTSxtHnvIzOGZhDgzR+f+dG1d5D2fj5EynSVEZEDhF1zcvGJRp55PW19kRkxeYlkik2L5FMsXmJZIrNSyRTor8xgTxHWOpKJB/Ldsp2yHGiv6vIXXCQxsA4SGNksTdIg6fNRDLF5iWSKTYvkUyxeYlkis1LJFNOe1QkRfRrr137v0Gd3oDtm1KdtTtELueUI29v9OvLTy/Fmy+kIrewFJW1ept17o5+XTJvCtJO5AGANfp1+6ZUbFyVjL3pts8fz1+5AV8fPq4mz+OU5pUq+rWjy4jjOYV4bOEMZ+wG0YjilObtK/q10dBms4696Ne73R39CvTEvj4yfyp8vPnNheR5ZHO+eW/0a3l1PeoaDVi3dB7q9IZ+X8vo18FzxleR6HRBjH4VYdDRr8NJiujXkopa3Kiqw+sfHYDZbIGhtQM7Pj+CbX//2H3bZ/Tr4DgyZNHRoY78fzEwe9GvTmneu6NftWoVcn8sxXOptuHovdGv46PDRUW/LpqTgEVzEgAAdXoDdu3/ps/GJXJXTmleqaJfiTwZZxXRsOAMIelwVhGRm2HzEskUm5dIpti8RDLF5iWSKTYvkUyxeYlkis1LJFNsXiKZYvMSyRSbl0im2LxEMsXmJZIp2aZHdhm7sftgFmr1zVAICiTGRWPNT+Y6a3c81nt/+Bgf7NjV5+/spW+8tu1F/PrVl6QsyyM5ZUqg2WzG9j+lYcv6ZdBpVHh3z2E8l7oIo8O01nWyzl1BRU0DfrYiCbmFJbhw9SaeX7sYN6vroQnwh1atQkVNI3buy8T7W9ahy9iN0opaTBobiW6TCR9+cQzLkxIxbWJ0v7VwSqA0HPmiMRLHpVMCpUiP9PH2wqQ7+c1eSiXGRITcF2pH5M5knR7Zq62jEwXF5feFsRO5M9mmR/Yymc34c9r3ePiByQjTqft8LdMjnYNJkNJwu/TIXl9knEZ4sAZL5tmPKmV6pPR4zSsdt0uPBID0rHy0dxqxYeVC0bUIABR2PgxyXFVNHdQqP1eX4ZbMZnOfy50WQFdw7RYOZJ61pkeuSJ5hkx5p7O7G3vRslN9usKZHhunU+PrkRRw9XYDwu06JN69fBpPJjN/82wFEhATBS9nTjIvnTkbyrHhn7A7d450/f4XXn1vl6jI8iselR5I02LzOx/NHIpli89KwSOHlitPxtJlIpnjkJZIpNi+RTLF5iWSKzUskU2xeIpn6P53seRmVlh9EAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig: plt.Figure\n", + "axs: Iterable[plt.Axes]\n", + "fig, ax = plt.subplots(1, 1)\n", + "ax.boxplot(\n", + " data_fetch_signature,\n", + " tick_labels=[\"\"],\n", + " )\n", + "# ax.set_title(\"Signature Fetching\")\n", + "ax.set_ylabel(\"time (s)\")\n", + "fig.subplots_adjust(left=0.1, right=0.5, bottom=0.05, top=0.5, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-fetch-signature.pdf\", bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": 449, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig: plt.Figure\n", + "axs: Iterable[plt.Axes]\n", + "fig, ax = plt.subplots(1, 1)\n", + "# ax.set_title(\"Verification Duration by Size\")\n", + "ax.boxplot(\n", + " [data_verify_binary_full] + list(data_verify_binary.values()),\n", + " tick_labels=[\"all samples\"] + [f\"{k/1024:.1f}\" for k in data_verify_binary.keys()] \n", + " )\n", + "ax.set_xlabel(\"Size (KiB)\")\n", + "ax.set_ylabel(\"Time (s)\")\n", + "fig.subplots_adjust(left=0.1, right=1.5/1.6, bottom=0.05, top=0.75/1.5, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-verification-duration-by-size.pdf\", bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": 450, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig: plt.Figure\n", + "axs: Iterable[plt.Axes]\n", + "fig, ax = plt.subplots(1, 1)\n", + "# ax.set_title(\"Binary Fetch Duration by Size\")\n", + "ax.plot(\n", + " [0, 1] + [f\"{k/1024:.1f}\" for k in data_fetch_binary_by_size.keys()],\n", + " [None, None] + [np.average(v) for v in data_fetch_binary_by_size.values()],\n", + " linestyle=\"--\"\n", + " ) \n", + "ax.boxplot(\n", + " [data_fetch_binary] + list(data_fetch_binary_by_size.values()),\n", + " tick_labels=[\"all samples\"] + [f\"{k/1024:.1f}\" for (k, v) in data_fetch_binary_by_size.items()]\n", + " )\n", + "ax.set_ylabel(\"Time (s)\")\n", + "ax.set_xlabel(\"Size (KiB)\")\n", + "fig.subplots_adjust(left=0.1, right=1.5/1.6, bottom=0.05, top=0.75/1.5, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-fetch-binary-duration-by-size.pdf\", bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": 451, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#fig: plt.Figure\n", + "#axs: Iterable[plt.Axes]\n", + "fig, ax = plt.subplots(1, 1)\n", + "par = ax.twinx()\n", + "par.plot(\n", + " [0] + [f\"{k/1024:.1f}\" for k in data_no_tuf.keys()],\n", + " [None] + [np.average(v2)/np.average(v1)*100 for (v1, v2) in zip(data_no_tuf.values(), data_full.values())],\n", + " label=\"TUF overhead\",\n", + " linestyle=\"-.\",\n", + ")\n", + "par.set_ylim(0, 240)\n", + "par.set_ylabel(\"TUF overhead (%)\")\n", + "ax.plot([], [], label=\"TUF overhead\") \n", + "ax.plot(\n", + " [0] + [f\"{k/1024:.1f}\" for k in data_full.keys()],\n", + " [None] + [np.average(v) for v in data_full.values()],\n", + " label=\"TUF + BT\",\n", + " linestyle=\"dotted\",\n", + " ) \n", + "ax.plot(\n", + " [0] + [f\"{k/1024:.1f}\" for k in data_no_tuf.keys()],\n", + " [None] + [np.average(v) for v in data_no_tuf.values()],\n", + " label=\"BT only\",\n", + " linestyle=\"dashed\",\n", + " )\n", + "\n", + "ax.boxplot(\n", + " data_full.values(),\n", + " tick_labels=[f\"{k/1024:.1f}\" for k in data_full.keys()],\n", + " )\n", + "ax.boxplot(\n", + " data_no_tuf.values(),\n", + " tick_labels=[f\"{k/1024:.1f}\" for k in data_no_tuf.keys()],\n", + " )\n", + "\n", + "ax.legend() \n", + "# ax.set_title(\"Cumulative Duration by Size\")\n", + "ax.set_xlabel(\"Size (KiB)\")\n", + "ax.set_ylabel(\"Time (s)\")\n", + "fig.subplots_adjust(left=0.1, right=1.0, bottom=0.05, top=1.35, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-cumulative-duration-by-size.pdf\", bbox_inches='tight')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analysis\n", + "\n", + "- We see that the fetch duration scales linearly to the size of the binry, which is the expected outcome.\n", + "- We also see that signature verificaion does not scale with the size of the binary and and effectively has no correlation with the size of the binary. The lack of correlation is related to the fact that the hash for the binary is calculated during the binary fetch step. \n", + "- We see that the TUF repo update is the a costly operation. Which requires significant effort for parsing, networking I/O operations as well as cryptographic operations such as calculating hashes and verifying signatures. However, the amount of data transferred via the network is much smaller than the data size of the binary.\n", + "- Furthermore, we see that similarly to the TUF repo update we also spend a significant amount of time in the verification step. Which is also biased towards parsing and cryptographic operations. However, we can speculate that this result could indicate that using DER instead of JSON is more performant, as this step requires JSON parsing while also requiring fewer files to be parsed and nonetheless requiring similar durations.\n", + "- We see that the verification overhead falls with increasing binary sizes.\n", + "- Fetching and verifying target files is relatively fast, because the TUF repo update step already established trust in the corresponding hashes of the target files. Therefore, it is only necessary to download them and then compare the hashes." + ] + }, + { + "cell_type": "code", + "execution_count": 452, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dict_keys([41440, 106992, 172520, 238060, 303592, 369132, 434668])\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(data_by_size.keys())\n", + "chosen_sizes = {41440, 238060, 434668}\n", + "data_by_size_filtered = {k:v for k,v in data_by_size.items() if k in chosen_sizes}\n", + "n = len(data_by_size_filtered.keys())\n", + "fig, axs = plt.subplots(1, n)\n", + "legend_added = False\n", + "for (ax, (k, vals)) in zip(axs, data_by_size_filtered.items()):\n", + " sizes = [\n", + " np.average([dict_to_seconds(d.fetch_target_files) for d in vals]),\n", + " np.average([dict_to_seconds(d.update_repo) for d in vals]),\n", + " np.average([dict_to_seconds(d.fetch_signature) for d in vals]),\n", + " np.average([dict_to_seconds(d.fetch_binary) for d in vals]),\n", + " np.average([dict_to_seconds(d.verify_binary) for d in vals]),\n", + " ]\n", + "\n", + " labels = [\n", + " \"Fetching Target Files\",\n", + " \"TUF Repo Update\",\n", + " \"Fetching Signature\",\n", + " \"Fetching Binary\",\n", + " \"Binary Verification\",\n", + " ]\n", + " wedges, texts = ax.pie(sizes)\n", + " if not legend_added:\n", + " ax.legend(wedges, labels,\n", + " title=\"Operations\",\n", + " loc=\"center left\",\n", + " bbox_to_anchor=(-0.5, 0, 0, +1.75)\n", + " )\n", + " legend_added = True\n", + " ax.set_title(f\"{k/1024:.1f} KiB\")\n", + "fig.subplots_adjust(left=0.18, right=2, bottom=0.05, top=2, hspace=0.4, wspace=0.15)\n", + "fig.savefig(\"plots/benchmark-time-shares-per-file-size.pdf\", bbox_inches='tight')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analysis\n", + "\n", + "- We see that the overhead of fetching signatures and TUF target files is neglible compared to the other steps.\n", + "- Smaller file sizes are dominated by the overhead of verification." + ] + }, + { + "cell_type": "code", + "execution_count": 453, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "40.5 KiB (('fetch_binary', np.float64(7.744213899999999)), ('fetch_binary', np.float64(7.992618181818182)), ('fetch_binary', np.float64(8.487051642857143)), ('fetch_binary', np.float64(9.161874749999999)), ('fetch_binary', np.float64(9.758790777777778)), ('fetch_binary', np.float64(9.88472109090909)), ('fetch_binary', np.float64(10.629747424242424)))\n", + "40.5 KiB [np.float64(7.744213899999999), np.float64(7.992618181818182), np.float64(8.487051642857143), np.float64(9.161874749999999), np.float64(9.758790777777778), np.float64(9.88472109090909), np.float64(10.629747424242424)]\n", + "104.5 KiB (('verify_binary', np.float64(6.878273999999999)), ('verify_binary', np.float64(6.931651363636364)), ('verify_binary', np.float64(6.708610071428572)), ('verify_binary', np.float64(7.0387799375)), ('verify_binary', np.float64(7.046635222222221)), ('verify_binary', np.float64(6.777353727272727)), ('verify_binary', np.float64(7.009265848484848)))\n", + "104.5 KiB [np.float64(6.878273999999999), np.float64(6.931651363636364), np.float64(6.708610071428572), np.float64(7.0387799375), np.float64(7.046635222222221), np.float64(6.777353727272727), np.float64(7.009265848484848)]\n", + "168.5 KiB (('fetch_signature', np.float64(3.9600812999999997)), ('fetch_signature', np.float64(4.014341)), ('fetch_signature', np.float64(3.7923429285714283)), ('fetch_signature', np.float64(4.1221663125)), ('fetch_signature', np.float64(4.129424111111111)), ('fetch_signature', np.float64(3.8606706363636363)), ('fetch_signature', np.float64(4.093336696969697)))\n", + "168.5 KiB [np.float64(3.9600812999999997), np.float64(4.014341), np.float64(3.7923429285714283), np.float64(4.1221663125), np.float64(4.129424111111111), np.float64(3.8606706363636363), np.float64(4.093336696969697)]\n", + "232.5 KiB (('update_repo', np.float64(3.9348990999999995)), ('update_repo', np.float64(3.988519909090909)), ('update_repo', np.float64(3.7672989285714285)), ('update_repo', np.float64(4.0966318125)), ('update_repo', np.float64(4.1037566666666665)), ('update_repo', np.float64(3.834977818181818)), ('update_repo', np.float64(4.0682240606060605)))\n", + "232.5 KiB [np.float64(3.9348990999999995), np.float64(3.988519909090909), np.float64(3.7672989285714285), np.float64(4.0966318125), np.float64(4.1037566666666665), np.float64(3.834977818181818), np.float64(4.0682240606060605)]\n", + "296.5 KiB (('fetch_target_files', np.float64(0.0832039)), ('fetch_target_files', np.float64(0.07922945454545455)), ('fetch_target_files', np.float64(0.07867521428571429)), ('fetch_target_files', np.float64(0.08435625)), ('fetch_target_files', np.float64(0.07863511111111111)), ('fetch_target_files', np.float64(0.08193781818181817)), ('fetch_target_files', np.float64(0.08582227272727273)))\n", + "296.5 KiB [np.float64(0.0832039), np.float64(0.07922945454545455), np.float64(0.07867521428571429), np.float64(0.08435625), np.float64(0.07863511111111111), np.float64(0.08193781818181817), np.float64(0.08582227272727273)]\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaYAAAENCAYAAABEqsEuAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAAsTAAALEwEAmpwYAABcNUlEQVR4nO3dd3wUZf7A8c/M9k1200NCICEQOqGE3qSpYEGx915PveKdd97pnVf1+t3PcnpnO3s9BUQRUelSpQakhhZIQno2ZbNt5vfHhoWQBBIIyYZ8368Xmp195pnvTjbznXnmmedRAjW5OkIIIUSYUNs7ACGEEOJ4kpiEEEKEFUlMQgghwookJiGEEGFFEpMQQoiwIolJCCFEWDG2xUbemLeC7D2HcERYeeLeWQB89PU6tuzOxWgwEB/t4LaZ47FbLW0RjhBCiBPU1Hp487OV5BWVoaBw66Xj6RIXxUuzl1BSXkVcdCT3XDGZCNvZP04rbfEc0+6DBVhMJl6btzyUmL7be5i+PZIxqCofL/oWgCunjjjboQghhGjEa58sJ6N7FyYM64M/EMDr8/P5N9lE2MzMGDeYBSu3UFPrbZPjdJs05fVOTcJuM9dbNqBnCgY1uPn0rgmUuWraIhQhhBAncNd62X3wCOOH9gbAaDBgt1rYsusgYzMzABibmcHmnQfbJJ42aco7lZWbdzNiQHqT7y/fsJPlG3cBcN8VE7CYTW0VmhBCnBM8Xh//mb0i9HrisD5MzOoLQHF5JZF2K69/uoLDR8pITYrj2gtH4ap2E+WwA+CMtOGqdrdJrO2emOav2Iyqqowa1LPJMhOz+oZ2YIXLRUDT2iq8RkU5nVS4XO0aQ0tJzGdfR4sXJOa2Eg4xW8wmHrtrZqPvaZpObkEJ108fTXpKAu8vXMMXK7PrlVEUBUVR2iLU9u2Vt3LzbrL3HOKuWee12QcWQghRX7TTTrTTTnpKAgBZ/XpwsKAUZ4SNisrgbZaKyhocdmubxNNuiWlbziEWrt7KA9dMw2xq9ws3IYTotKIi7cQ6IygoqQBgx/48khOiGNynO6uy9wCwKnsPg/uktkk8bdIr7+XZS9l1oIAqdy3OCBszzxvKgpXZ+P2BUNfD9JQEbrp43Cnrau/LYQiPy/KWkpjPvo4WL0jMbSUcYjaoKpGRkU2+n1tQwpufrSSgacRHR3LrpRPQdZ2XZi+ltKKKuKhI7rnyHOou3pra+5cL4fElaymJ+ewL93gDAZ2SCh8+n8bRP3pVVdHa+Z5tS0nMTVMAk0klLsqEwVD/9sipElM4kTY0ITqJkgofNotKQowpdE/XoKrt3pmopSTmpum6TmVNgJIKH4mx5lOvEKZkSCIhOgmfTyPSbpCORucwRVFw2A34fB0rcZ9IEpMQnYQOkpQ6AUVR6FD3ZxohiUkIIURYkXtMQnRSY94MAIFWq2/1LYZWq0t0bnLFJIRoM+9v17huboAnljd+D2RXqc7Kw6duiJrybvMT6u++0Vh0oGGd20t0/r62Y9+LOVfJFZMQos18vEvn2fNVEiMav9e1q0xnRwmMSzn798L6xyn0j2ud7QQ0HYMq9+9aiyQmIUSb+PNqjcNV8PAijfN7KByuhL3lOn4N7h6iMrYrvLRJxxOAzYUBbh2kMD5F4e/rdHaUBK947hqsckHdeM8vbNT45rCOxQB/mawSZ2s6MazL13ljq0a1D344QmVCN4X1BTrvfKfx96kGXtqscaQaDlfpHKmG6/opXNc/2KD0s8UBjtSANxBcPqtPcPmUdwPM6q2wrkBnSqrCzhKNv0wJNmeuydP5eJfGnydL8+bpkMQkhGgTj45RWZUX4F8XqLy7XWd4EvxynIFKr86d8zVGJqncM1RhRwk8Mip48H9ug0akCd6eGTzAuzzBBOX2w6B4he8NU3l2vcbc3Tp3Dm46MeVX67x6scqhSnjwS42RyQ3vYhxw6fzrApUaH1w7V+OqvjpGVeHxcSpRFoVafzDOKWk6URYFtx8GxsMPRxjQdZ3rP9Epq9WJsSp8lqNzaS+5U3K6JDEJIdrcmjyd5bnwznfBe0XeABQ0MiXbunyd3088doB3WoLJx6TChG7BZf3iYG3+ybc3LU1BVRRSnZASCQcqGpYZl6JgNiiYDRBjhVI3JEbABzt0luYG70UdqYFcF0QlgEGBKanBeBRFYUZPhQV7dS7NgOwinSfGS9Pe6ZLEJIRoczrwx0kqaVH1D97bipv3BI5RPfZMlkFRCGgnX+/EFNFYyjAfd4FjUMCvw/oCnXX5Oi/PULEaFb63MICnrt+F2UC9+0qX9lJ4ZLGG2QBT0xSMcs/ptEliEqKTWn2Lod2G9xnTVeHDnTo/GRlMMDtLdfrGKtiNCjW+Y0lmVLLCRzt1Hh4ZPMi7PDoxtpZv7+sDOhf30smrgsNVkBoFW4tOvV61DxxmsBoV9lfobDvJOgl2hXgbvJat88wF0ox3JmTvCSHa3B2ZCn4Nbv5U44ZPAry4KZgchyfBvgqdWz4N8OV+jTsyFSq9cOMnAW7+NMD6I6e3vaQIhTvnazz8tcajo1UshuZdzYzpCgEdrpsb4PkNGgMTTl5+RrpCYgSkR8nV0pmQ0cVPQ7iPIt0YifnsC/d4Dx2ppVuX+hO9yYCoretvazX6xMBlveuf87d1zE39rjvK6OJyxSSEEK3gts8C7CnTmdFTrpbOlNxjEkKcE/6b3XCEh6lpCndkts359+uXyDNLrUUSkxDinHBHpsodme0dhWgN0pQnhBAirEhiEkIIEVakKU8IIZqwvaS1Oi23zvQirTXobLiTxCREJzXhi+WtWt+K6RNbtT7ReUlTnhCiQ3hk/VYqfX4APjxwmJtWfMtvt+xoUR0Prd3CjorKBstXFJbw5t7cVolTnDm5YhJChDVd19GBvw0fFFo2Ozef/xuRSaLV0irbmJAYx4TEuDOu52isqtI5mtzOFklMQog28cKufSRaLVyV2hWAV/YcwGYIPvuzqKAIn6ZxXpd47spII99dy4+/3cqAaAc7K6r42/CBPLR2Cy+PHcbLew6QV1PLI+u3MjUpgc8OF/DuhBEYVZVqv5/bV24IvW7MgrxC/rRtNwFd5xcD+zAg2sH8w0fYUVHJjwdk8GT2TiKMRna4Kimo8XJ1t3SGx8ZTGwjwrz3fURPwE9B1ZnVNY2hMHMWeWv5v1zbSIyM5UF3NiNh4avx+rk/tCcCyogLy3TVcV/danJo05Qkh2sS0pAQWFxwbBXVxQRExZhO5NW5eGjOU/47LYqerik2lwTkpDtW4uaJ7Mm9NGE6S7djwOo8O6kO8xcwzIwdze69UhsVEs7KoFICv8os4LzG+yaQE4AlovDYui5/0z+CP23Y1WqbY4+X5UUP4fu+BfHR4PwAmVeWBjP78asAwftInkw8O7UPXg50jCj1uJick87tBWVzYJYUtFaX464YgWll8hPHxXU5/x3VCcsUkhGgTfZyRlHl9FNd6KPP6cJhM5FRVs664jDtWbQTA7Q9wqMZNF5uFJJuFQdHOU9Z7abcuvLPvEOd1iWf+4SM8OrD3ScufnxwciXVobBTV/kDovtXxzkuMQ1UUutrsuHw+INhMN/vQAXZVVaCgUO714vIH34s1W+gVGYzVajDQzxHFlopSkq12/LpON3tE83eUkMR0Or5atBy/3090dBSxsVHEREdhNpvbOywhwt6ULvEsPlJMqcfH1KR4Ctwebu7ZnVndk+uVy3fXYjU0b4ifwTFR/P27HDaUlqPpOj0dJ08CzZmbyVRvLqXgVdGa0iIq/T5+2X8oRlXl51vW4au7KrKo9WOdEJ/E/IJckqw2uVo6DW2SmN6Yt4LsPYdwRFh54t5ZAFS7Pbw0ewkl5VXERUdyzxWTibC1zo3Ms+2m2x+iuLi03rLIyAhiY6KJiYkiJiaa2JioutfRpKQkcetNV4fK5ubm4XRG4nQ6QpOdCdHWVkyf2OajXk9NTuAv23ZT7vXz3KjB7K2s5qU9B7gwORG70UBRrQfjafxNzEhJ5LdbdnJ7z+6nLPt1QRFZcdFsLqsg0mgg0tS8w6A74MdhMmFUVXa4yinxepos2zPSQZnXw8HqKn49MKvZn6O9Pfbch1jNJlRFQVVVHrtrZrscq9skMY0dksHkEf15bd6x5yYWrMymX49kZowbzIKVW/hiVTZXTh3RFuGcsfFjR5JfcISysgpKS8spK6+gqqqaqqpqDuYeblC+d0Z6vcQ0fvLlVFXXsH/XapxOBwC/+NUfOXDgEDEx0cRERxEbGx1KdPUTXjQ2m7XBNoToCHpGRlDjD5BgMRNf929/dQ33r9kEgM1g4InBfVvcq+3C5ERe2n2A85MTT1nWrKrcsXID/rrOD801OjaR5/Z8x2+2bSDNHkmS9eQzFo6IiSe3ppoIY8dqmPrxzTOItB87xrTHsbpN9ljv1CSKy+s/O7Bl10F+fPMMAMZmZvCPtxZ0mMT08fsv15t3R9M0KiurKC0rp7S0nNKyCsrKggmrtLQch+PYHCh+v5/Y2BiMJmO95StXfUv21uY9k2GzWYmJiWbjmgWYTCYAnn3+v7hcLu647Xq6JgebDnJz86j1eIiLjcHpcLTGRxfijL0xfni919empXBtWkqDcm+eUO5/k0Y1+jPAlrIKpnSJx3GKq5/nRg1udPnFKV24OCX4d/N4Zt/662SNA8BhMvGL/kMaXf+3gxpeFe2ucnFBl4afq6Npj2N1u6VyV7WbKIcdAGekDVe1u71COWOqqhIV5SQqykl6j9STljUajWxat7DB8n/85dcUHCmitKycstLyYJI7muDKKoLLy4LL3e5aVKUilJQA3nr3Y3bv3suVsy4JJaY//fU53v1gLgD9+mZw7903cd3Vl8kVlzin/HP7HlYXlfHX4QPbOxQAavx+nty+ie72CPo7o9s7nBZRUHj6nYUoisLEYX2YmNW3XY7VYXGNqSjKSe+1LN+wk+Ubg90675g5luTE+LYKrUlRzlP3FmqJqZMnNKucrutUV9dQXu6qF8PPH3mQ3Nw8+vXpFVqelJRIr55pHCksZsfOPfz4p7/lqT8/y/1338ID991Gly6nmCc6DLT2fj7bwjnevCIvhka6UTe2LNwdH/MjjTTH/XXbbrLLKuotu7ZHNy7tltTCLbV8jDu70ciTmWfniqK5vytVVRt8F/MLi3nm/cWh10cTz/EeufUiYpwRuKrdPP3OQpLio+q9f6pjdWtpt8TkjLBRUVlDlMNORWUNDnvTZ/ETs/qGdmCFy9Xu01eHwxTaDmdEvRiuuHxG6Oejy3/12I/41WM/wufz8eXXy/nbP/7Npi3b+MOfnuYv/3iea66ayffuvZUB/U/evba9hMN+bolwj1fTtAYdHcJ5mvKmNCfmH/fv1ejyjvZZT9Tc+DVNa/BddNitPHbXzJOuF+MM9mh0RtgY2jeVfXnFLTpWt5Z2S0yD+3RnVfYeZowbzKrsPQzuc/ImMHH6TCYTN1w7i4umT2H1mg3869+v8fkXi3n73Y95+92PmTJ5HA/efztTJo2TXoLirJGRusObx+tD18FqMeHx+ti+N49LJg5pl2O1EqjJba1vS5Nenr2UXQcKqHLX4oywMfO8oQzpk8pLs5dSWlFFXFQk91zZvC6I4XBGGu5nxo05MeacvQf4z0tv8s57c6hxB9uMn3j8R/zo+/e0V4gNdLT9HO7xHjpSS7cu9c922/KKqfUSU+toTmLqiDFD07/ryMjIJtaAorJK/v2/RQBoms7IgelcPGEIVTW1p3WsPhNtkphaUzj84Yf7AagxTcVcVlbOa29+yKuvv8f8OW/SvXtwHLPNW76jW0oycXExbR1qSEfbz20Z75g3W37V8Nx4HxFRZ+eAcq4e5DtizHB6iSmchEXnB9F+YmKiefgH9/DDh+5Crbuxqmka9z34KAdzD/Pp7NfJGpbZzlGKs2HJikGnLtTcuoDvXb6t1eoTnVvH644jzgr1uN4+FRUueqR1Jz4ulsxB/ULLc/YeCA1aKcTpOJT3FmvXz+S7nY82+n5l1Q5KSpedsp5lK0eeURyuyq2s2PLUGdXRmKKSr6muyWn1ejsbuWISDcTERPPeW89TWVUdelaquLiUiVOvoHdGOg/cdxtXXD5DxgcULXY4/32GDHoJq6XxbttV1TuorNpGXOx5ZzUOp2MQ/Xu0fktAccki4mInEWFvvFdgYzTdj6rIofh4sjdEkxyRxwbD3L1nH47ISLK37uB73/8Fv3vyn9xz143cdvM1xMREt1+QosPYuee31Nbmkr3teyQmzMDtzqW6Zg+67qdH6gPExkxk/8Hn0AIeKlwbSOt2N7Gxk9iT8xSVVcFmwh6pD5AQfwEAe/c/TUnpUlSDlcz+z2A2N/58Y2HxF+w/+AIKKkajg2GDX6esfC3zd7/OxWOex+0p5av1P6O6tpAuMUM5VLSSqyd9iC9Qw9r19xHlzKKichMWcyKD+j+LwWAlr+B/5Bd8iKb5sNlS6d/nj1RV76CkdDEVFd9yIPc/DOr3T3bsfoJe6Y/gdAzC6ytj/abrGDtyIflH5lBc8hWBQA26rjF44PPsznmq3v6Ij5vaZr+bcCNNeaJZxo4ZzuZvv+SZf/yefn0zyC8o5HdP/h+ZWefz6GNPsnffgfYOUYS5vhm/xmxOZEjmqwQCbqKjRzN86HsMyXyVnH1/R9d99Eh9iISEGYwc9hGJCRdx4OC/MRgjGZk1m5FZs4mODg5FpGlunI4hjMz6mGjncPIKPmpyuwcO/pshA//DyKyPGTTg2Qbvf7vzeVLiR3P91E/o1fUCqtz5ofdq3AfpmnwDo7LmYjQ6KSr5EoCEuPMZPvR9RmZ9jN3ek/wjHxPlHEZc7BR6pv+EkcM+wmY7ebfqyqrtDOz3T4YNfo0DuS822B+BQM3p7OZzgiQm0WxWq4Wbb7ySb5bM4X/vvcjUKeOpcbt56dV3GDnuEm654wesWr1e7kOJUyotX8nBQ6+wbuNVbMq+A033UOvJb1CurHw1Kck3hF6bjMGRCBTFRFzsJAAckQOo9TQcPPkop3MoO3Y/Tl7B/0Bv2DU+v2QDGSkXAZDaZSIW07ERE2zWFByR/Y5tpzYPgOqa3WzccivrNlxBYeFnVNfsaekuIDZ6LCZT8PM0d390FtKUJ1pMURSmTh7P1Mnj+W77bl548Q0+/Ggen33+NZ99/jVZQwfxvftv48rLL5IHdkXjdBjU75/Y7en1Frsqs5u1uqIYj323FAO63nT3+b4Zv8ZVuYWS0mV8u+laRgz9oNlhKurx91FVdD04qeCOXb9kUP+niYzsR/6ROZRXrGsyzqPzOWla/WkyVMNxo5M3sT86K0lM4owM6N+bZ//5e3712A955b/v8epr77Fh01Zef+MDrpp1cXuHJ05i8oStrVZXS0dRiI0Zx6H8d+jd8zEURaGyajuOyP4YDREEAtWhcjExYzmc/y69e/4cAJ+/InTV1Fxu90GcjsE4HYMpLVtOraeg3vtJscPIyVvAsN53k1v4DR7fqZ9F8weqMZsT0DQfhUWfYjYHB04+MX6rtSuVVdtwOjIpKv6yxfujs5KmPNEqEhPi+cXPHmLL+q/4x19/zU9//L3Qe1uyt/PYE3/iwIFD7RihCCdp3e9H1/18u/FK1m64nH0Hgvd+oqNGUVOTw7qNV1FY9Dlp3e/D73exdsMs1m24kvLytS3eVs7+v7NuwxWs3TALp2MokRH1By4d0e8BcgtX8t6iy8nJ+wK7JR6z8eSz4KanPcSGzTeyccst2G09Q8sTE2aQe+i/fLvxatzug3RPuZ28/Pf5duPV+PxlLd4fnZWM/HAaOtqIBNC+Md/34KN8+NGnPHDfbfzhtz9r9nodbT/LyA8nF66jKAQCXhRFRVWNFJRuYtnm33HtlI+B8I35VGTkByFO4aHv3Y5BVbnv7ptDyxYsXIzH4+WSi6ZhbOcZPk/nIN+4ps+IW2L1LYZWqUc0T6U7ny+//TG6rqGqJiYP/W17h9TpSWISZ13moP48/+wfQ681TeM3v/8Hu3bvJbV7Cvffews33XBlveemhGipA7n/obC4/iScifEXktb9vpOuFx2ZxjWTm+5uLtqeJCbR5gKBAHffcQMvvPgG+/bn8tiv/sQf//Ict918DffefRPdUpLbO8QG9IAfPeBD9/vQ/d66n73oAW/dMh+oKpakDFSz7dQVilaX1v2+UyYh0TFIYhJtzmQycfedN3LHbdexYOESnv/P66xavZ7nXvgvL7z4BrMum85999zCoP59KS4pweP1kdQlgYiI4PTO+/YfZO++g6T36E7P9DQA9h/IZfGSlXi8Xrweb/D/Pl/dzz68Xi/euv97PF58Ph/XXDUzNMFi9c6VFM37K/beo0m8vK4HWOkh9v/tSnS/t9HnXxpjS8+i+wOvAaBrAXzFBzHFp6F0wFlihWgvkphEuzEYDFxy0TQuuWgaGzZm88KLbzDnky/4aPZ8Ppo9v17ZD975N+dPnQjA2+/O5h9Pv8hjj36fRx6+H4Ct23byk0d/16LtDx0yMPSz7qvFeyQHU1y3YwVUI7qvNvizoqAYzChGM4rRVPd/M4rBFHxtMKN53VjThoRW9xbkcOCfV2Pp2o+0h489OxOoLscQEd2iWIXoTCQxibCQNSyTl174K79+/GFefOUdPpozn0AggMlkxGI2YzabQmV79Uxj8qRxpHZPCS3rkdaN2265pq6sGYvFhMlkCr62mDGbTJgt5tD7ZrOJPr2PdfO19RpJ2k8+QrUde+rf6Ewk48nVwYRkaN6fyvGjXvirSjBGJWJO7HHcslL2/nYyptgUrKmZWFMHY03NxJLSH9Uog+IKAdJd/LR0tG7MIDGfTOv1ymuc5vOgmoLdtN37N3HoxXuPXYkdZTBi6doPW+pg/nzjEIZnZZLeI7XJkTNao7t4+aetNx8TwNjbTj4fU0fset0RYwbpLi6EOIWjSQnA1mMoGb9fifdIDu6D2dQezKb24Ba8hXvx5G7Fk7uV+755B4DY2GiyhmZyz503csH5Z3caiI6i4usLcUx8H9Xc9MzKtbtfxNr73jaMSrQ2SUxCtDHFYMTStS+Wrn1hzNUABGqr8ORuxX0wm1H6VtZv2EJhUQlfLVoe6qAB8MXCJcz55AuqnNOJ7D+xvT5CWKvd81KrJCZdCyCD47QPSUxChAGDNRJ77zHYe4/h7VsM6LrOoUP5rFu/mbFjhofKLVryDe//7xPiZqSFElPtoe9wbfgMW2om1tRMjDFdw3bw3EDNYarXPYhz0hwAanP+i+6vwV+yDoOzL/7Sb0EPYB/8e4wxmWjecmo2/BStthBjzBCODogKULXuB+i1BegBD5b0m7GkXYN7+z8h4MG17CoMkRlEZP0Z76F5ePa9ja77MEYPxpb5SxSl8YeY17w9gi59rqUifxXpo3+Jt+Bwo+uWfz4SS+rV+IpWolrisWf9FdUSi79iB+7s36EH3Bjs3bEN+T2quWVj+wlJTEKEJUVR6N69K927d623/I7brqN3Rjr/KskKLavZtYry5W9SXvfaEBlX17EiE1tqJpbugzBYO8C9hUAtzvM+wl/yLTVbfoVz0hxqdz2PMTYLa5/v4TuyFG/ux6Hi9rqDvh6opXLF9ZiSL8DW/2E8+9/BeV7wgdlAZQ7evAVEjn8TRTVRk/17fIc/xdzt8kZD0PxuIhMG02Pkz6gpz8Gb90rj6wbcGKIGYhv4KLW7XqB21wvYMx+nZtMvsA96DGPcSNw7n6N29wvYB/68TXbfuUQSkxAdSL++GfTrm8HLx3V+sPcZS1zAR+3BbNwHtxCoKqH6uyVUf7ckWEBRMCf2JDD0RQIGM4rZVu++V7gwpQRHozfGjUD3VaH5XPhL1xMx/P+C73eZhHLcXEmefW/hK/gaAM1dgFZ9ANUcXa9Of/EaAhXfUbni+uCCgAfFHNt0EIqBuNTgDLkV+atPsq6KqeuMurgvpXr9j9B9lei+SoxxIwEwd7uMmvU/Oc290blJYhKtLpzGnusM485Zuw3A2m0AEOyu7ivJretUEUxUnrwdeI/koHlr8JUXA2BO7HmyKs8aRTHUf1hZ8x7/bv2yNN0c6Stei794NY4Jb6MYbFSuvB094GmkpI6522XY+j/crPhUgxlFPfadae66J4tVtJwkJiHOIYqiYI5PxRyfijPrEiDYXd2TtwODLQqD0YTuq0U1WYm+NDgfk7doP7rHjSk+FdV6euMVNrcbs2KJQ/eUonnLUQx2fEeWYkwYD4Av73NM8aPwl25AMTlQTA6MscPx5c3H0Ps+fIXL0Y/OleSvQjE5UQw2AlV7CZRvObYN1YSu+VBUE8b4MVSv+z6WnreiWuLQvBXgr0a1d20svHqikkdzcGtT62r48hdiTrk4GF9sVl3MTvwl6zHGDcd7aB7GuBEt3pdCEpMQ5zzVZMGWNgTV6sMUdUIzlg7oOjo6ynHNe/7yAnS/D9UagWqJQDFaTrygOS2KasLa536qVlyPYu2CGnncjK0GC5XLrkbX/dgH/x4Aa58HqNnwU1yH52OMGYpiC46jaEyYgOfAB7iWzESNSMcQPThUjTn1aiqXXYnBOYCIrD9j7fd9qtbcC7qGopqwDXq8WYnJHp3R9LoGG4Hyrbj2vIhqjsWe9bfgOkOfCnV+UO3dsQ/5/ZnvtE5IHrA9DfKw6smd7QdWW6I5TXnhFC+cvZhPNh+TrgWONWHp4CnYhR7whd5XVFMoSanWCBSDqd76Z/qwauXK27ENeARjdOs+9HsyZxJz+ecjib6o8enUzyZ5wFYI0Wkcf18FBcwJPdA81Wi11WieanTNR6CmnEBNOQCq0VKXpCJRLHbkUCJak3ybhBANKEYzBqMZQ0RMsKnP5wkmqqP//B40vweqSwGFA+4o0lK7nbLepjjGvdZqsTdH5Yob2Kx46y3LmPgnImL6NGv99rha6kzaPTF9tWYb32zajaJA14QYbps5HlM7z2gqhDiOoqCYrRjMVgyOONB1NK+77mqqCs3rxmA4dsXl8XjJPZRHVJSDhPi4dgy8aY4J7za7Wawz0TSNP776KdEOOw9edz7F5ZW8PHsp1W4PqUlx3HH5RIyGppuaAwGNgpIK3B4vNouZpLgoDIaWj57RrhmgzFXN4nXb+fV9szCbjLz48RLWbdvHuCG92zMsIcTJKAqqxY5qsQMJoGl0iT52L6ayqorKqioMBjWUmAKBAIGq8lbtSCFa36J120mKj6LWE7y/+PGi9UwbNYCRA3vy9vyVfLNpN5OG92uwXvbuXJZt2MmO/fkYDCpWs4lar49AQKNfj2QmZvVlcO/uzY6j3S9NNE3D5w9gMKj4fH6iHfb2DkkI0RKqisl0LNNERzkxGAwYj2v5qK6uwVeeD4BiMAXvT1ki6zpStPthSBC8UMjec4iLxg/m6zXb0HWdnfvzuWtWcADhsYMz+HT5pgaJ6S+vzyfCambkwJ7cdPG4esfw8soadh8sYPmGnSxYmc3Pbru4WbG06zcixhnB+WMG8dizH2IyGeifnsKAnikNyi3fsJPlG3cBcMfMsSQnxrd1qA1EOZ2nLtQK+v/rzB8yDWqderY/2PSozq29rdbQvN9T+MQLHTNmw3Ez9BrMZiyx9eeWMplMGGxRwY4UgUY6Ulgjg1dTFnv9jhhtFHPTwqvHZvNiBlVVG3yP8guLeeb9xaHXE4f1YWJW39DrD75cy5VTh1PrDV4tVbs92K3m0DajnRGUV9Y02NZNF40lJbHx40K0w87IgT0ZObAnhwub/51t18RU7fawZddB/vDg1ditZl78eDFrsnMYndmrXrmJWX1DO7DC5Wr3rtodsbt4a+lon7ujxQttF7P5Z1Nbra5dQK9XFjf5vs1mDc4OXNeRIlDXiUI/2pGiygNVJYCCarahWiIwRMSgGE1N1nmmApp26kJhprkxa5rW4HvksFt57K6ZjZbfsjsXh91KWnI8Ow/ktyimppLS6ZaDdk5MO/bnExftwBER7G8/rG8aOYcKGyQmIcS5Qf1mNobVn6B17U3gxseP60hRheaphsO7UavL8fcYiMF+bFRuzVMNiopqsoKiYPrlxfj+ML9Z21S2rkBP6AZdepylT1XHXYW68Wu0cY0PEHuU4dN/o+xcg953NHpcVzBb0YZfiOH9P6P1H4M+eNLZjbMROYcK2bI7l605h/D7A7g9Pt5fuJaaWi8BTcOgqpS7qpt9qyWvqIw3P1tJXmEZ8TEOrp8+mt6pSc2Op10TU6wzgn2Hi/D6/JiMBnbszyctuf2b6YQQZ4dh1Vx89/wNohOCC+p1pAB1/w4oOAiZk4OdJOr4y4+g+dyY49NQ60ZK91eWoHvdoKgoqgqKEvxZUUFVQz+btiwh0HcUekxScHSLuilBdF1vcnoQPRBAOUnvs0a5q1BXzT1lYlLXfobvN3OgjZosm+OKKcO5YkpwepWdB/L5avU27pp1Hi9+tJgN2/czcmBPVm3Zw+DeqY2uf+K+/Oirb7lq2gh6dI1n5/58Xp+3gj88eHWz42nXxJSekkBWvzSefOUTDKpK9y6xTBjWvOcITkc4DS4KnWOAUSGOMnz0TyjNx/jqz9GGTEEpyUM5sg8CAQIX3IbedxSGr14Hnwdr7k60KTei9R+DYc4z2A8Gb8Zr0+9CHzIZAOOXr2PYswHdYMY98x50e8N7c4b8vVi2r0bZuwn96zfx3/Z7DPu3oq75jL3eGvyOOLrc8xjRCQkUvvInPJqO58BulO69UUaej2H2f1B8HgK9R2Bc9znun7+JoqgYV36C4buVEPChDZyINuNODJ+/hFKSh/Gfd6P3HkHg0vsbxGP87+PgcWN8+n60KTeiFB5At9jQJl1Xr5xyaBeGec+D1w0RUfivfRSccZR/9RGuJfNQDAbMyWl0uf+Js/K7OuqKqSN4efZSPlm6ke5dYhk/tPEe039943Ouu2AUaV2DFxb+QIC4qEiMBgOxUZF4fS079rZ7d5iZk4Yxc9Kw9g5DCHGWBa56GHXXWvz3/QPDsg/RM4YRuPZn4K7C9OwD+HpnEbjwdpRDOwnM+iEAhvkvgi0C/0/fCFZSUwmA4q2FXsPwzrgL4xevYd2TjW/CFcGRyzUdXdeCP6cPwZ8xDH/PTAK9h2OK744WGYM2+lLMFQcJLHwXz5qv4NIbAPCXFVF51Y9AVbF98m98mRPw9x2BKXsFRnT8FUcwHNiOnr+Hqqt+AOhEfPEmyt7NBC66ByVvN1XXPIzRkdDowdV/x5OYfnkx/odfCn6+ha81sqP8GOY+g/+2P0BkNOqmxRgWvELg2p9RPv9d0v78DorJTKCmqnV/QXX6piXTNy04JmFCjINf3HnpKde547IJfLBwLdEOO7OmDOfiiUP4y+vzg6Pd+wNcP310i2Jo98TUEU13L0ZBo1ax4q77d/TnMjWKKrVjjEclRHtRdn2L8t1K1KUfBBf4vVBW2LDc7vX4b/zVsQV2BwC6wQSDJ2NQFJT0wSi712N0JjS6LdUaiTGqC4ak4L1rpWA/hi9exeSrwljrRrEd62ARPXoaCb17oWkaxYUH8d/yG0yKgj7yIlgxB2NkPKb8/RhydxHx/t9A11ECfpTiw+jRXYL1K4Yz6lmoFOWiFOzH+NJPgwt0DRzBwXfN3Xty5KUniRg2gYhhE057G60tIcbJg9edz/rt+/m/t79gysj+PPnQ1VTV1BJps6A2szfhUZKYWkrXidYqMKABFQ3e3mgayHZzsAdhij+fYd7s4xKXpV4SCwSSTuupaCE6Ph3/Lb+BxBPuWeRub97qBsOxe0WqiqI1v6nI+MGf8d/2e1IzM3CtWEDtzk2h90wRkURGBqf+KFEUDPbo4LaMwXtTxuguqJYItPNvQRtzQg+30gJQjVhSGj6A2iK6jt6lB/6HnmvwVvIP/0jtri1Ub1pJ2Wdv0f23r7b8XthZNLx/Dwb2SuHTZZv451sLuH76GJwRthbXI4npNHxim45Vr8V23D+r7sGm11KuHutJFKHX4NSrcOpNXXJfFfrp0wXfUFXtxm63ElH3z2630c1vokJ1Uqk6zvKnEp2N9y+LWq2ulg7vo/cZiWHlbAKX/yA45NHh3egpvdEtdhSP+1i53sMxrJpD4LKHggtqKkNXTc1msaF4agiNTeGpQXfGovv9VK35CmN04x2urD37481ehjZ0CuqmY13htb4jMHzxX7Rh54PFBhVFYDCGtnOm9ITuKNXlKAe2oacNhIAfpSgXPTENf2kRtn7DsGZkUrV2MZrHjcHe/i00uw4U8N4XqykpryI5IZqbLx7HmMxevLtgNanJcVw2aRhWc/O7/ktiailFwa3YcGM7ZReIHGMaBYaEUNIKJrBabFotRvz1rpaKSypwVVY3qOM8YKexF+stQwCICZQxzvNtvSsvt2oJ/VysxhJQ5Ncqwlvg/FswfPIvjP+8O9hUFZOM/86n0HsNRVn8LsZ/3oM25UYC027BMOdpjH+/E1SVwPm3omee16JtaUOnYvzf31G/mY3/ll8TmH4Hpmcf5HB0NJb0/ui1jSeTuBseouqFJ1EXvY3edyTUTaKo9xmJVngQ47/qkqXZhv+GX0BcClqPQRj/fid631GNdn5oFqMJ/y2/wTD3WaitBi2ANuEq9ITuFL78JJq7Gl3XiTr/yrBISgBvfLqC6y4cTb/0rmzfm8cHX67lxzfP4JFbL2L5xl389fX5/Oqek/dWPF6nmo8pnOfdqaysobrGTXVNLTU1taH/L9hVQ64hhX2mNAC6+fM4z7O6yTrn2GZQowa73g73bCJOK8OtWKlSIihXoyhXnbhUJwHl9C7/O9r8Rh0tXmif+ZjO1JnOx9QeThWz5qllZ6U52KV90yLUTYvw3/6HNoquceE6H9OjT7/P43dfhjPCRpmrmhc/XsKjt18Ser+qppZIu/UkNdQnp9ZhwuGw42jk4bXHDtY/AOUbEvnMNi14BaYda0I82rRYqxw78MRoFcRrDa/rNGCvMY21luBzC6oeIEJ3U6VEoDfxXIcQnY3nwC6MbzwdnOXXFoH/mp+1d0hh66ppI/nTq58S7Yyg2l3LjTPG1nu/JUkJJDF1OAHFSIUSRQVRcIoT65WWkdh1NzbdjVOrIlqrIFpz4dCr8CrHxjKL0SqYXrsEPwYqVEfoyqpcjaJCcQaTnSQsEebUr99C3bK03jJt8CS0aTefVn22PoPxP/zyaa2r5O/F8N4f6y80mvB///nTqi/cjRrUkxEDelDl9uCwW5t8cLm5JDGdw2pUOzU0vApT9QCG4wanNOteahQrdr2WOK2cOK28Xvk5thnUKMF6DuUVYTSoxMZGYTbJ16cj0fW6/5yjJxnatJtPOwm1Nj25Z+hZpTbftq636awirio3zkgbqqqetAfe0XLNIUeWTkhTDGjHXW7lG5OYY7wYk+4lWnOFrqyiNBcReg01yrEv0/KVmygqLgfA6YwgPjaKuLp/SYmxOJ0Rbf1xRDPlViv0swdQTIZzNjl1drquU1kTwGRqu8dQ/vn2F/RO7cKYzF70SElAPe67pek6+/OKWL0lhz25R3ji3lnNqrPZianW68Nd68VmNbeo25/oOHyKmSJDPEWG47rPnnCGnRAfjabplJW7cLmqcbmq2bs/D4DhQ/syfsxgABxaFd38eXVNgk7cik0Ohu3s398ZuH9AgO4RgVb/VRzyn7pMdcNOp+3qXIxZAUwmlbiotjtGP373TJZv2MVb81dSXF5FfHRkaKLAkvIqEmIcTMzqy7UXjmp2nSdNTIcLy1i+cSdb9xyipKI6dJCKi4pkYK8Uzsvq26KhzEUHdMIR7PzJI4HgFMrlFZWUlFZQXFJBSWkFyUnHElpioIhhvq2h115MoftW5aqTvcYeaIo8XNyWXD6Fv2w+O40kzelJeHUH7P3YEWNua0aDgSkj+zNlZH9KXdUcLizDXevFbjWTkhhDzGm0ojT5LX159hLyiysYMSCdOy4/j6S4KKwWE7UeHwUlFew+UMCrc5eRHB/F3VdMPoOPJToig0ENNeH1yWj4foXqZKexZ13ToAsLXhK1EhK1EnwY2WNMD5Ud7tmMXzFQUZe4XIqjTZOWMXFlm22reSaeskRHjFmc+2KdEcS2QnN+k4lp1KBejc7RHmGz0KtbIr26JTJj/GC27M494yDEuafYEEexIS74Qtex6bVEaS6idRdG3R+6ElN0jQz/vrohnoI0FFyKgzJDNHuMPeo3LQrRhuQEoH00mZgaS0pnUk6cnnPiD+PoaBmqjQK61H+LYLf2ox0uorUKIvVqonUX0X4X+YZEiurKdvXn0y2QT4kaQ4kaQ4XqRJfmQCHOOc1qcF63bS/dusSSHB9NQUkFb322ElVVuHHGGJLio89yiK3nnDjIn2M0RSXXmEIuKaFlBt1PtOYiVivjiHrsaqlr4AgZ/v1ksB8APyrlajQlagxFhjgOGru1dfhCiLOgWaebc5dsJMIaHFHgo6/W0aNrPL1Tu/DugqaHxhHidAUUIyWGWHabeuFWjz2HlWPswXpzJvsN3ahUIjCiEa+V0tefQ1/fnlA5RdcY4t1Kqv8QFa7guGJCiI6jWVdMVTW1OCNt+Px+cg4Vcu9VUzAYVB75x7tnOz4hQsoM0ZQZoqGuJ6xJ9xKrlRMXKKs3FJNTr2SgbxcAr78DVquZxIQYuiTEkpgQQ7eURCzyyEObkxaLc9N/5y5r1qMgd1zW/P3drMQUabdSWOricGEZacnxmIwGvL5mPAQgOqW2OgDpQEndPwAjweepNH+AbZURxHj89NLBXevhYO4RDuYeAeDGay7AEhcNwMHcI3Sp8VBmMeGVubGEaLGEmGNT2le5a1m9JYfM3t2Ji4qg1FXNlt25jM1spOvuSTQrMV08YTBPvToPVVFCXcO378uTZ5hEWHIbDWyLCc7Z88cLJ1BV5eZIUSlHCkspKikn9rg/pLXrv2PSkeBAt1VGA6UWE2UWE6VmI2UWE/4WzrwpRGdz6XlDQz8/8+5CHrzufHqnHuvktCf3CPNXbG5Rnc1KTOOG9GbEgOBzJ0fHR0tPSeDuKya1aGNCtDVFUUIjt2f0bNg5omtyPFvKKoj2+oj0B4j0B0itrgVgj8PGhvjgxI/mgEakz0+52YSmyggWQjRm7+EieqbUn+I+vWsCew8VNbFG45pMTD5/AJPx2FPGJw7YeXSwvhPLCdGRjBudyc/Ky1F0HafPT6zHR6zHR4zHR4nl2AjsSW4PY4oq0IAKszF4ZWU2UWoxUWE2ynQhQgDdu8QyZ8l6Zp43DLPJiNfnZ96yTXTrEtuieppMTE++/Aljh2QwelAvohuZJ6iisobVW3NYtWUPv7nvipZ/AiHCiK4oVJhNVJhN7Gtk5m4dqDAZcfj8xHiD/yA4BbhXUZiblhhKTjEeH26DSq1BlfEBRady28wJvDpnGQ//7R3sNjM1bi9pyXHcOatlsw43mZgeufUiFqzM5g8vz8VutdAl1hkakqiw1EWNx8vYwRk8cstFZ/xhhAh3uZE2ciNtGDWNaK8/dFUV6/HhU5VjV0y6zuT8Uky6jldVcJmMVJqMuEwGXHVXWh6DtDCIc1N8tIOf3X4JpRVVVFS5iYq0ERvV8llzm0xMkXYrV58/kllTsth3uDg4MJ/n2MB86V0TMEgvJtHJ+FWVYquZYuuxZj71uOekTLpOhdmI0+fHrOnEe3zEe3yh99fFO9lX1wKR4PaQWOutS1xGKk0GAtLZQpwDYqMiiXFGoBOc+gKoNx3GqZyy84PRYKB3apd6vSyEEMdox/3B+VSVRV3jQNexaBpOrx+nL4DD58dZ13niqGS3l34V9edVqDaqzPlsGYnxMYwbndlmn0GI1lBeWcO7C1azJ/cINbXeeu+98Nhtza6nWb3ydF1nxabdfLttL1VuD7+653J2Hyygosod6q0nhDiOouAxGCiyGShqYtLOfJsZDXD6/Dh8fhy+ABF+jYO5R3C7PaHEpOs6/337MxwRdmJinMTGOEiuqcVlMlJjNEjHCxE23p6/ErPJyI9uvJC/v7WAR265iHnLNjEoo2XDhTUrMc1bupHt+/KYOmoA73y+CoBoRwQffrlOEpMQp6nIZqHIdmzECkXXifQFeG5QH5Tjkk1lVQ1VVW6qqtzkHwk+Tnz0GXq/AqsTosmLsAIQ4fNj1HSqTEYC0q1dtLG9h4t46qGrsZhNKEC3LrHceul4/vL6Z0wc1qfZ9TQrMa3asofH776MSLs1ND5efHQkxeWVpxX88WpqPbz52UryispQULj10vH07JZ4xvUK0dHoikKl2Uiv9JR6yx2Rdu685VLKylyUlldSVubiy5xcHD4/9oAW7P1XJ8NVQ19XDTpQbTSE7l25zMa6bu5mhDiRz+/nb28swB8IoGk6Wf3SmDlpGMXllbw8eynVbg+pSXHccflEjCfpvKMqCmrdfVKb1UxldS1Wi4nyypoWxdOsxKTpOhZz/aIerx+L6cxnw/xg4VoG9kzhvqum4A8EZKgjIU6gKAqRETYiI2x07xa81/vLmuC9KZOm4T/u6sqrqrhMBiJ9gdADw3W92ik1G/kqpW60dl0nq8SF22ggoCgEFAVNAb+ioCkKJRYTtXXPJ1r9AcyaTkDhuLIKAQXpDn+OMBoMPHzzdKxmE4GAxl/fmM/AjBS+WvMd00YNYOTAnrw9fyXfbNrNpOH9mqynR9d4tu45xLB+aQzsmcLLs5dgMhpJS27ZnGrNyiyDenXjwy/Xcc0FwTnbdV3nk6UbyDzDuZjctV52HzzCbTMnBIMxGE6ajYUQ9flO6MW3PSaS7TGRqLpOhC+As67ThcPrp+q4E8kIf4CMSneT9S7rEkNBXWLKqKxhQHl1o+WqjAbmdz/2pP+Hcxbh8/kx1P0tG4xq8P8GlZTqWg7XNTk6vH5Sq90NEt3R1wV2S6hTSYTPj6rXvacGk6/cV2tdiqJgreuYE9A0AgENBYWd+/O5q+4ZpLGDM/h0+aaTJqY7Lp/I0U6q11wwiq/WbKPW62PayAEtiqdZienqC0by2icrePhvbxPQdH7417fpn961RaPFNqa4vJJIu5XXP13B4SNlpCbFce2FoxqM/Lx8w06WbwyOFn3HzLEkJ54bM5pGOZ2nLhRmOlrMHS1eaJ2YtbpmwUqzkcONvO9TVTbGOrAGNFRdx6DrGHRCP7uPax48ehWm6tSVO1b+xJhLyyrxeLwnbg4AR8yx51mifH4GNpHsAOakJuI1BJPP8GIXScf18AoAlWYjFSYjBTYLBxxN9C45hc723cgvLOaZ9xeHXk8c1oeJWX1DrzVN46lX5lFUVsmkEf1IiHFgt5ox1J38RDsjTtkkZ7ceu2dqNhm5eMKQ04q1WYnJZjHzvWum4qpyU+qqJsZpJyqy4WgQLaVpOrkFJVw/fTTpKQm8v3ANX6zM5rLJWfXKTczqG9qBFS4XFS7XGW87HHTEz9HRYu5o8ULbxOw1qOyOimhW2V1REexqrKyuhyZ0OxrztVdMxe8P4A8ECPgD+AMagUCAQCDAgu3H5sxymQxsi46olwwNuh5KfoHjrojcRpVKoyFUzqrpRHv9RHv9aIoSSkwRPj9jC8upMBtxmYJDRVWYjcEk28gVVmf7bjjsVh67a2aT76uqyi/vuZyaWg///t9iCkoqWrwNnz/AZ8s38+13wR7c//fITXy39zBHSlxMGdm/2fW06CaRyWQg2mFH1wllzsaGK2quaKedaKed9LpB/7L69eCLldmnXZ8Qog0pCtoJi2KiGxnPqY4r58Cxn80mtjVzTqx1CdH1Xhvrng+L8vmpOm6cziivn9i6f1AbWu5VFFxmI6sTo6mpK2/QNHRdr9f7UQTZrRb6piWx91ARNbVeApqGQVUpd1Wf8nj/4ZdrKa+s4c7Lz+PZ974EIDk+mg+/XNf6iWn7vjzenr+SkopqOH42UEVp0UNTJ4qKtBPrjKCgpIKkuCh27M8jOSHqtOsTQpz7/KpKqdVMqbV+D8NCm5lFybGhpOX0+ony+rFqGnEeH57j7seNLyznpdc+ITbWSVxMVPD/sVHExTixHdeFv7OorK7FYFCwWy14fX6278vjwrGZ9E1LYsP2/Ywc2JNVW/YwuHfqSevZtPMgv3/gymB38aNjRzojKK9sutm2Mc1KTG9++g0XTxjCiIHpmFt5JPHrLhzNq3OWEdA04qMjufXSCa1avxCic2hsuCgASyBApC9Q77kumz9ArS9AXn4xefnF9coPGZTBpAnDADAFNJw+Py6zsUFHk3NJRVUNr89bgabr6LrO8P49GNy7O8nx0bw8eymfLN1I9y6xjB/a+6T1GA0qmqbXW1ZZXUuEzdqieJqVmHyBAOOGZIT6p7em7klxJ233FEKIM+ExGBoMnPtFSjwLJoykpKyCklIXpaH/u3A4jt1PS6j1MqGwHIAag1p3/8pYd//KRPk5MuVJty6xPH73ZQ2WJ8Q4+MWdlza7nqz+PXht3vJQD+6Kyho++HJtiwdiaFZimjZqAAtXbWX6uExpkxVCdHyKQmSkjchIG2ndk0KLdV0noNW/c1ZaNyivPaBhd3tJdgd7COrAx2ldgs9zAemVNQSU4IjyLpOxU04oOWtKFh8vWs/vX5yL1+fniRc+ZvywPlwysWW985qVmIb168Gz7y5kwcpsIu3121//8ODVLdqgEEKEK0VR6j1LmRdhJS/CiqLrRPgDoftWUXVDPx3fPDiorApbIJjUNKDKFBx5o8BmYa/zzHsxdwRGg4FrLxjFtReMorK6lki75bQuZpqVmF78aDEZ3buQ1b9Hq99jEkKIcKcrClUmI1UmI3mN9JxXdJ29Dluo40WkL4Cz7p9fVdhL50hMEBw4oaCkAs8Jo/j065Hc7DqalZhKyqt4/O7LWjSfhhBCdBa6orAt5lhXeVXTcfiCSaqmE41ms3Lzbt77Yg0WsxGz8Vh6UZSWta41KzEN6dOdnfvz6Z/eteWRCiFEJ6OpChUWExWW5j2rda6Yu2Qj9145ucXTXJyomb3yNJ7/4Gt6p3bBEVF/+I8zHZZICCHEuUHTNQb0PPMLmGYlpq7x0XSNjz7jjQkhhDh3TR+byfwVW7h44pAzuvXTrMR06XlDT3sDQgghzl2/eOaDY2MR6joV1W4Wrt5KxAkjaPzx+9c0u84mE9PugwX0Tg3279+xP7/JClrS00IIIcS55Y7Lz2v1OptMTO8uWM0T984CgkMSNaalPS2EEEKcW/qkBS9gNE3jiRdm8+v7ZmE6w8eKmkxMT9w7i3Xb9jJyYE+efEiSjxBCiKapqoqqKvj9gTNOTCcd/O7t+avOqHIhhBCdx9SRA3hp9hJ2HSigqMxFUVll6F9LnLTzg45+sreFEEKIkPe/WA3A9r159d9o4RRJJ01Mmqazc3/+SdOTdH4QQggB8MLjt7dKPSdNTP6AxpuffVNvbsDjSecHIYQQJyp1VVNeWU3PlMTTWv+kicliMkriEUII0SylFVW8PGcZh46UogBP/+xm1m/fz3c5h7nl0vHNrufcnZJRCCFEm3p7/ioyM7rxfz+9CYMhmF4GpHdl+768U6xZ30kTk3R+EEII0Vz784uZPi6z3nBENqsZt8fbonpOmpie/unNpxedEEKITscRYaWo1FVvWV5RObFRkS2qp1lj5QkhhBCncsHoQfzrg6+ZMS4TTdNZt20vn3+zhenjMltUjyQmIYQQrWL80N5E2Cws37iTGGcEq7fkcNmkYQztm9aieiQxCSGEaBWapjG0bypD+6aeUT3SK08IIUSr+NnT7/PO56vYk3vkjOqRKyYhhBCt4gc3XMi6bft4Zc4yVEVhxMB0Rg3sSUpiTIvqkcQkhBCiVaQmxZGaFMdV00aw60AB67bt5Z9vLyAq0s6v7rm82fVIU54QQohWlxQXRVJ8NLHOSErKq1q0rlwxCSGEaBU1tR427jjA2m372He4iP7pXblw7CCG9OneonrCIjFpmsYfX/2UaIedB687v73DEUKITqXUVc1rnyzHVe1GQWHCsD5MGzWAareHl2YvoaS8irjoSO65YjIRNkuT9Tz69Af06pbIqIHp3HfVZOzWpsueTFgkpkXrtpMUH0Wtx9feoYhO6Kba+9s7hBNsO2WJvxZ0vJhF+DIoCldPG0lqchy1Hh9PvTqP/uldWbVlD/16JDNj3GAWrNzCF6uyuXLqiCbr+cMDVxHlsJ9xPO2emMpc1WTvOcRF4wfz9Rr5cou2N7S8vSMQ4aqznABEOeyhhGK1mEiKi6K8soYtuw7y45tnADA2M4N/vLWgycQU0DS27T3M9n35VLtribBZ6Z+ezOhBvUIDujZXuyemD75cy5VTh1PrbfpqafmGnSzfuAuAO2aOJTkxvq3CO6uinM5Tlgm3s/koZ257h9AizdnH4UZibhudLeb8wmKeeX9x6PXEYX2YmNW3Qbni8kpyj5SSnhKPq9odSljOSBuuanejdbtrvfzfOwsprahiYEYK3ZPiqKiqYfbi9Sxdv4Mf3Tgdm9Xc7FjbNTFt2Z2Lw24lLTmenQfymyw3MatvaAdWuFxUuFxNlj2ZcDv7qXCd+uwn3M7mT3fft5eOFi9IzG2ls8XssFt57K6ZJy1T6/Xx4kdLuPaCUdgs9ROJoigox40afrzZi9fjsFv58c3TsZhN9ep7+eOlzF68nhsvGtvsWNu1u3jOoUK27M7lsec+5JXZS9mxP59X5y5rz5CEEKJTCgQ0XvxoMaMG9WRYv+DYds4IGxWVNQBUVNbgsFsbXXfzroPceNGYekkJwGo2cf2M0WzaebBFsbTrFdMVU4ZzxZThAOw8kM9Xq7dx5+XntWdIQoizJNxaLKTDxjG6rvPGZ9+QFBfF+aMHhpYP7tOdVdl7mDFuMKuy9zC4T+Nj4Lk9PqKb6PQQ44w46a2axrT7PSZx7gmv+2Jy8BHiVHIOFbImO4eUxBj+8NJcAC6fMpzpYzN5afZSvtm0m7ioSO65cnKj6yfEONixv4ABPbs2eG/HvnziozvofEx905Lpm5bc3mGIVhBu98WEECeX0b0L/3789kbfe/im6adcf9qoAbz2yXKunzGaoX3TUBUFTdfZuOMA73+xhllTsloUT9gkJiGEEB3TuCG9qXZ7eH3eCl6ZvYxIu4WqGg9Go8olE4YybkjvFtUniUkIIcQZu2DMICYO60vOoUKq3LVE2qz07JbQoHdfc0hiEkII0SqsFhMDe6WccT0yurgQQoiwIolJCCFEWJHEJIQQIqxIYhJCCBFWJDEJIYQIK5KYhBBChBVJTEIIIcKKJCYhhBBhRRKTEEKIsCKJSQghRFiRxCSEECKsSGISQggRViQxCSGECCuSmIQQQoQVSUxCCCHCiiQmIYQQYUUSkxBCiLAiiUkIIURYkcQkhBAirEhiEkIIEVYkMQkhhAgrkpiEEEKEFUlMQgghwookJiGEEGHF2J4bL3VV89ony3FVu1FQmDCsD9NGDWjPkIQQolN6Y94KsvccwhFh5Yl7ZwFQ7fbw0uwllJRXERcdyT1XTCbCZjnrsbTrFZNBUbh62kh+c98VPHr7JSxdv4O8ovL2DEkIITqlsUMy+P71F9RbtmBlNv16JPP7B66iX49kvliV3SaxtGtiinLYSU2OA8BqMZEUF0V5ZU17hiSEEJ1S79Qk7DZzvWVbdh1kbGYGAGMzM9i882CbxBI295iKyyvJPVJKekp8e4cihBACcFW7iXLYAXBG2nBVu9tku+16j+moWq+PFz9awrUXjMJmMTd4f/mGnSzfuAuAO2aOJTnx3EheUU5ne4fQYh0t5o4WL0jMbaWzxZxfWMwz7y8OvZ44rA8Ts/o2e31FUVAU5bS33xLtnpgCAY0XP1rMqEE9GdYvrdEyE7P6hnZghctFhcvVliGeNR3xc3S0mDtavCAxt5XOFrPDbuWxu2a2aB1nhI2KyhqiHHYqKmtw2K2nvf2WaNemPF3XeeOzb0iKi+L80QPbMxQhhBAnGNynO6uy9wCwKnsPg/uktsl22/WKKedQIWuyc0hJjOEPL80F4PIpw8nM6NaeYQkhRKfz8uyl7DpQQJW7lp8/8wEzzxvK9LGZvDR7Kd9s2k1cVCT3XDm5TWJp18SU0b0L/3789vYMQQghBHD3FZMaXf7wTdPbOJIw6pUnhBBCgCQmIYQQYUYSkxBCiLAiiUkIIURYkcQkhBAirEhiEkIIEVYkMQkhhAgrkpiEEEKEFUlMQgghwookJiGEEGFFEpMQQoiwIolJCCFEWJHEJIQQIqxIYhJCCBFWJDEJIYQIK5KYhBBChBVJTEIIIcKKJCYhhBBhRRKTEEKIsCKJSQghRFiRxCSEECKsSGISQggRViQxCSGECCuSmIQQQoQVSUxCCCHCiiQmIYQQYUUSkxBCiLAiiUkIIURYMbZ3ANtyDvHBwrVous74ob2ZMW5we4ckhBCdTjgdi9s1MWmaxrsL1vDDGy8kxmnnj69+yuDeqXRNiD4r20tclnhW6j1tt526iMR8hjpavCAxt5VzNObT0dbH4lNp16a8/XnFJMY6SIhxYDQYGDkgnS27DrZnSEII0emE27FYCdTk6u218fXb9/NdzmFuuXQ8AKuzc9h3uIgbZoypV275hp0s37gLgPuumIDFbGrzWI9XWVOLw25t1xhaSmI++zpavCAxt5VwiNnj9fGf2StCrycO68PErL5A84/FbaXd7zE1x8SsvqEdGA6eeX8xj901s73DaBGJ+ezraPGCxNxWwiHmSGj3GJqrXZvyYhx2yiqrQ6/LXdXEOOztGJEQQnQ+4XYsbtfElNY1nsJSF8XllfgDAdZ9t4/Bfbq3Z0hCCNHphNuxuF2b8gyqynXTx/DMu1+iaTrjhmTQNSGmPUNqlonD+rR3CC0mMZ99HS1ekJjbSrjHHG7H4nbt/CCEEEKcSEZ+EEIIEVYkMQkhhAgrHaK7eGvQNI0/vvop0Q47D153PgDF5ZW8PHsp1W4PqUlx3HH5RIwGQ731issr+e1/5tAl1glAekoCN108rkH9f3/zc66eNpK0rvEUl1fy9DsLuX76GCJtFlZn53Dd9NGs3Lybjxd9S3SknYCmkxQfxR2XTcRsCv4a3pi3guw9h3BEWHni3lmhuqvdHl6avYSS8irioiO554rJRNgsoff35xXzl9c+464rJjG8f49GY3NVuTEZg5/tBzdeiDPCVq/Mys27OZBfwg0zxqDpOm/MW4GqKNxy6Xiee/8r7pp1Hnarhe899TopCdHogKooRDnsHMgrbhAzwOJ121myfjuqojIooxtXTRtBIKDx5mffcLCgBE3TGZPZixnjGw598tq85ew+cASbJfjM2m0zJ9A9Ka5emZ0H8vlq9bbQ73Pukg0cyC/me9dM470Fq5k2eiBdE6J57LkPsZpNaJpOqauaCKsZq8XMhGF9mDZqAACfLNnA5t25KIAjwsZtMycQ7bCzZmsOC1dtRdd1rGYTN140lm5dYtskXlVR8PkDGAwqmq6joNSL+dCRUt7+fBUer4+4qEjunHUeNou53nu1Hh+KAr+481JMxvp/7vOWbWTFxt047MHv0uVThpOZ0a1emeLySp7/4OvQ73b5xl0s37CTH954IV+v/Y7eqV3on9613nfM5w/gDwSwWsxomk5WvzRmThoGgK7rzF2ykQ079qMqCucN78vUkQPQdZ0PFq5la84hzCYjt106gdTk+vsPzu53+foZY0jvGt/gOPHKnGUczC/GYFDpkRzPTRePw2A4dk7fnn9/188YQ69uYTY6RSvoNIlp0brtJMVHUevxhZZ9vGg900YNYOTAnrw9fyXfbNrNpOH9GqybEOPgl/dc3qztlLmqefbdL7n6/JEM7JUCBHu8HDW8f3roobVX5izl2+/2MW5IbwDGDslg8oj+vDZveb06F6zMpl+PZGaMG8yClVv4YlU2V04dAQQT7uxF39K/Z9eTxnXn5efVi6Mpuq7zzvxVBDSNWy8/D0VR+P71F4TeNxsNoX2xLecwsxd9y/evv6BBzDv357N510F+efflmIwGXNVuIPggnz8Q4Il7Z+H1+fnNf2YzYmA68dGOBrFcOW1Eo3/ojZm/YjM5hwp56LrzMRkNoQcFj/rxzTMIBDRyDhfxvy/X8ujtl/DUq/Pon96VrgnRXDB2EJdNzgJg0brv+Gz5Jm66eBzx0Q5+fPMMImwWtu45xFvzV/LzOy5tNIbWjjfSbmX3wSO89PFi/vKj66n1+OrF/OZn33DVtJH0SUvim027+XLVVi6bnEVA0/jvJ8u547KJdOsSS1VNLQa18caRaaMHcOGYQc2KeXV2Dku+3c7DN00nwmbhsrpkc9TR71hVTS2/ev4jfvu9K1FQ+Osb8xmYkULPlERWbdlDWWU1v7n/ClRFCX0vtuYcprDUxe++dyX78op4Z8GqJvfz2fouz1m8niF9UhscJ0YN6smdl08EgklqxaZdoeNEe//9zVm8np/cctEp6+1oOkVTXpmrmuw9hxg/9FjPGF3X2bk/n6y6A8nYwRlsPsMhOCqq3Dz97kIum5zFkD6pQPAs+V/vf9WgbEDT8Hj92K3m0LLeqUnYbeYGZbfsOsjYzIxgnJkZbN55LM7F325nWL80HBGt81T5+wvXUO32cPtlE1EVBYDHnvuQqpraBmVrvT7ioiMbjXnphp1MH5cZOks8eoaoKODx+gloGl6fH6PBEDrLP11frt7K1pzDPHDttNDV59/f/JwDecX1ykU57MQ6I7BbzVgtJpLioiivrAGoF4PX60ep++y9uiWGrk7TUxIoc9WcUawtiRfAZDSE9t2JMR8pddE7tQsA/Xt2ZcPOAwB8tzePlMSY0JVdpN2K2kRiaq5vv9vHFyuz+cENFxBZN4LBa/OWs377/gZlvT4/lrorvoCmEQhoKAT359L1O7lkwpDQd+voZ9uy6yBjBvdCURR6piTirvVSUXlm+7ql32WjQW1wnADIzOiGoigoikKPrvH1vgPt/fd3/PHjXNIprpg++HItV04dTq332FlQtduD3WoOnUlGOyNCf/AnKi6v4smXP8FqMXHZpKzQweBEr81bzmWTsk561rx++z5yco9QUe2mS6yTwb1P/ayAq9pNVN3Dbs5IW+gss8xVzaadB3n45hns/3TFyarg9U+DTQPD+vXg4gmDQwfe463btpek+Gh+cvOMJs+wvf4Af3hpLr5AgIoqNw/fNL3RcoUlFew5eIS5SzZgMhq4atpIenSNJ6tfDzbvOsijT7+P1xfgmvNH1muWPN4nSzYwf8Vm+vZI5oopw0NJ7ng5hwopKKngsbtmYj3JUFX/eGsBAEVlldxz5WSKyyvJPVJKesqxs9g5izewJnsPNquZh2+a0aCObzbvZlDdVXBbxws0iLlrfDSbdx1kaN80NmzfT5kr+IBkYWkFCvDMuwuprK5lxMB0po/NbHQ7S77dzprsHNKS4riqid9FSUUV73+xhsfumklUZNMPXb46dxlGo4HCUhdXnT+Sp16ZR1FZJZNG9CM9JSH0Gb79bh+bdh7EYbdy7fTRdIl1Ul5ZQ4wzIlTX0b/HqEYe8jxb3+W05DiunDqi3nHieIGAxprsHK69cDQQ3n9/Hd05n5i27M7FYbeSlhzPzgP5LV4/KtLOUw9dTaTdyoH8Yv794SKeuG9Wo2f5/dK7snZrDuOGZITOhE90tClP13XeXbCahau3tmh4+aNnbgAffrmWK6YOD51ZNeXOy88jxhlBrcfHfz5azJrsHMYMzmhQLjUpjoKSCvblFZHRvfHke3xTwt5Dhbz2yQoevHZag3KarlNd6+HR2y9hf14xL328hD88eBX78opQFJU//+A6qms9/P2Nz+mX3pWEmPpNeVdMHo4z0oY/oPH2/JUsXJXNJROHNthOQoyDmlov2/flkdWvR5P74GjTWFGZi3+8tYAIm5VrLxhV7/c4a0oWs6ZkseCbLSz5dnvovggEmyZXbtrNI7c23mxyNuP959tfkJYcx4sfLakX862Xjuf9hWuZv2ILg3t3D90fDWg6e3IL+cWdl2I2GYPrJ8XRL71+c9OkrH5cMmEIKAqfLNnIR1+t49aZExrE4rBbsVstrN++n/NHD2wy5qPNVZXVtfzl9c/40U3TsVlM/Pt/izlcWEZKYgx+fwCT0cBjd81k444DvPnpCh659eIm62xsG2fju7xwVTYLVmaTmhTHroMFjZZ/Z8Eqeqd2CZ2Yhsvf3xP3Xt5oouvIzvmmvJxDhWzZnctjz33IK7OXsmN/Pq/OXUaEzUJNrZeApgHBITiiGzk7MxkNoaaLtOR44mMcFJa4Gt3W9DGDSOsaz4sfLQnV2xRFURjcuzu7Dx455WdwRthCzRoVlTWhwSAP5Jfw8uylPPbch2zcfoD3FqxmU11zzvGOnolaLSZGDkxnXyNNRgBd4qK458rJvDx7KXlFZaeMq2e3RKrctVS5PQ3ei3bYGdY3DUVRSE9JQFEUqmo8rNu2j4G9UjAYVJwRNnp1S+RAfsN4ohx2FEXBZDQwdkgG+5uI2Rlh46HrzufDhWvZuf/UJx6xzkjcHh+9U7swrF9ao2VGDerJxuP246Ejpbz52Uq+d83U0HehreJNiHHisFt54cNFjBrUs17MSfHR/PDGC3nsrpmMPO4+XYzDTu/ULkTarZhNRgb16sbBgtKGsUTaUFUVVVGYMKw3+xv5PQCYTUYeuv58lm/YyZqtOaeM2RFhJTUpjn2Hi7BbLfRNS2Lb3sMARDvtoc8wtG8qhwqD37Nohz10xQdN/z2ere9yda0Xt8fLY8/WP04c9emyTVTV1HL1BaNCy8Ll76+ykWa+ju6cT0xXTBnOn35wLU89dA13XTGJfj2SubPupmLftCQ21LWRr9qyh8G9UxusX1ldi1aXZIrKKiksrSQ+puGN+qOuvWAUVouJNz/9Bl0/+bPLe3ILG1wpNGZwn+6syt4TjDN7D4Pr7l89+dDVPPXQNTz10DUM65/G9TPGMLRv/YNtQNNC7dOBgEb2nkOknGSOlV7dErlxxlj+9f7XlFZUnTSuguJyNE0nopF27qF9Utl5IHjmeaSkgkAgQKTdQqwzInRA9nh97M0rIikuqsH6RxOxruts3nnwpPPCdImL4r6rp/Lq3OXkFpQ0WU7XdV6Zu5RAQOOiE65Sj5QeO9nYvCuXLnUxlVZU8Z+PFnPH5RNDyxpzNuIFqKiq4XBhGd0SYxpcrRxt0tV0nfnfbOG8uoGOB/RM4XBhGV5f8F7e7oMFJMc3vY8BNp0iZmeEje/fcAFzF29gW87hk8ZcUlHFgfwSEmIceH1+tu/LC/2Oh/ZJZef+4Pdi18GCUG/Xwb27s3pLDrqus/dwIVaLuUEz3tn8Lo/N7IXdauHJh66ud5wAWLFxF9/tPcxdsybVuzoKl7+/yCaawjuyc74p72SumDqCl2cv5ZOlG+neJZbxQ4O94zbvOsiB/BIumzSM3bkFzFu6CYMabEK76aKxTd4TgeCV0O2XTeBf73/Nx4u+ZdAJ3W+P3mPSCZ4l3nZc08nLs5ey60ABVe5afv7MB8w8byjjh/Zh+thMXpq9lG827SYuKjJ0z+Fk/vDSXH55z+X4/QGeefdLApqGpun0S09mwimGRxncpztV7lqeee9LHjmhx8/RNm4AneB9jr++8XmDmMcN7c0bn37D716cg0FVue2yiSiKwqQR/Xhj3gp++5856OiMG9w7dJP+2fe+5JZLxhPtsPPq3GWhM8FuXWK58aKxJ425R9d4bps5nuc/XMSPb27Y7v6Ptxbg8wUoKq8k2mHnmXcXAse6SM9ZtJ4jpRUoikKsMyK0vc+Wb6ba7eHdz1cBoKpqaITmsx2vqii4PT78AY19ecWh/X405nXb9rF0/Q4AhvVNZdyQYPNQhM3C+aMH8sdXP0VRYGCvbmTW3ct889NvOC+rL2ld4/l40bfkHilFURTioiK56RQxx0c7eODaaTz73lfcf/WUBu+/OncZJqOhLuYAb9SdnA3v3yN0L3X6uExenbOcr9duw2I2ccslwd6IgzK6sTXnML96/mPMJgO3XXrs76Ktvsu3z5zQaCeRdz5fRWxUJH957bPgvu6X1mgz7fHaO+aOToYkEkIIEVbOvVQrhBCiQ5PEJIQQIqxIYhJCCBFWJDEJIYQIK5KYhBBChBVJTKLTWrM1h6ffWdjq9eYVlfPUK/NO+RwbBJ+T+uFf3go9K3cqi9dt5+NF355piEKENekuLs5pe3KP8PHX35JXXI6qKCTFR3PtBaPo0YyRnk/Xf/63mKz+aYwc2BMIDsJ5yyXj6V83JNC6bXt5d8Fq7r96Kn3Skuqt+/c3P2ff4SIMqoqiKnRLjOWGGWNISQxOc+3z+/nV8x/z2F0zG0ydIMS5olM/YCvObW6Pl3+9/xU3XjSW4f174A9o7Mk9gtFw9hoKKipr2HkgnztnTWz0/VVb9vC/r9bx4HXnNzmPzvXTxzBhWB80TePT5Zv579xlofHRTEYjA3ulsDo7p9nTVQjR0UhiEueso2MaHr1yMasqA3oeGx185ebdfLNpNz+97WK+WJXN/OWbQ+/5AgFGDerJ7TMn4q718uFX69i65xCKojBuSAYzzxva6BP32/flkZoU12BSPoBlG3Yyd8kGfnD9BaG5eYrLK/nlvz7iX7+4tcGI0qqqMmJAOl+szK63/Oj8S5KYxLlKEpM4ZyXGOVFVldc+Wc6IAemkpyQ0OZzU9LGZoakhSl3V/Pm/nzKifzoAr81bgTPCyu8fuBKPz8+/3v+aGGdEaGy64x0uKmt0TL1l63ey59ARHr5peqMz4DbGHwiwduve0JQRRyXHRXPoSMNBWYU4V0hiEucsm8XMI7dexBersnlr/kpcVW4GZXTj5ovH4Yxs/P6M1+fn3x8uYurIAQzK6Iarys22nEP84yc3YjYZsZhNnD96AMs37mo0MdXUehsdVHP7vjz6pCXRte5e0cm8v3ANH329Dp8/gNFo4P6r6o9LZ7GYcHsanzNIiHOBJCZxTkuOj+b2mcH7PQXF5bw6dzkffLmWu6+Y1Gj5Nz/7hi5xTqaPC149lVRUEQhoPPr0+6Eyuk69Se2OZ7daqPX6Gyy/4aIxfL5iC299+g23XDr+pPPnXHfh6OA9Jl0nJ7eQFz78mh/fPCN0peXx+LBZmp5kUIiOThKT6DSS4qMZOziD5Rt3Nvr+gpVbOFLiqjcZYKwzAqPRwN9+fEOTs4oer1tiTGiKkuM5I2z86Kbp/P3Nz3l3wepTjj4OoCoKvVO7kBDj4Lt9eaHElF9S3uzmQCE6InmOSZyzCorL+XL11tAEdKWuatZ9t6/BPRuArXsOsXjddu6/Zmq92YejHHb6p3flf1+tw+3xouk6RWUudh1ofJbT/uldyS0oxedveNUU7bDz8E3T2ZZzmA++XNusz7D3UCH5xRV0jY8OLdt94AgDTzLFuxAdnVwxiXOWxWxiX14xX635DrfHi81iZnDvblw5bUSDst9u30dlTS2//ffs0LJRg3py08XjuOOyicxevJ7f/mcOtV4fCdEOLhzbeI84Z6SNvmlJbN6Vy4gB6Q3ej42K5OGbp/P3Nz7HZDQwsZG5ed77YjUf1iUuZ6SNyyYNC83r5fP72ZpziMcmzTytfSJERyAP2ArRyvKKynl93nJ+fselJ72XdDoWr9tOqauaqxpJrkKcKyQxCSGECCtyj0kIIURYkcQkhBAirEhiEkIIEVYkMQkhhAgrkpiEEEKEFUlMQgghwookJiGEEGHl/wEBl4wS1EoUggAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "host = host_subplot(111)\n", + "par = host.twinx()\n", + "\n", + "host.set_xlabel(\"Size (KiB)\")\n", + "host.set_ylabel(\"Time(s)\")\n", + "par.set_ylabel(\"Overhead (%)\")\n", + "sizes_x = []\n", + "sizes_y = []\n", + "data = { k: {\n", + " \"fetch_target_files\" : np.average([dict_to_seconds(d.fetch_target_files) for d in vals]),\n", + " \"update_repo\" : np.average([dict_to_seconds(d.update_repo) for d in vals]),\n", + " \"fetch_signature\" : np.average([dict_to_seconds(d.fetch_signature) for d in vals]),\n", + " \"verify_binary\" : np.average([dict_to_seconds(d.verify_binary) for d in vals]),\n", + " \"fetch_binary\" : np.average([dict_to_seconds(d.fetch_binary) for d in vals]),\n", + "} for (k, vals) in data_by_size.items()}\n", + "bar_x = [f\"{x/1024:.1f} KiB\" for x in data.keys()]\n", + "def helper(acc, a):\n", + " k,v = a\n", + " acc_ctr, acc_l = acc\n", + " acc_l.append((k, acc_ctr + v))\n", + " acc_ctr += v\n", + " return (acc_ctr, acc_l)\n", + "bar_y = [list(reduce(helper, vals.items(), (0, []))) for vals in data.values()]\n", + "bar_y = [d for (ctr, d) in bar_y]\n", + "bar_y = list(zip(*bar_y))\n", + "bar_y.reverse()\n", + "for x, y in zip(bar_x, bar_y):\n", + " label = y[0][0]\n", + " print(x, y)\n", + " y = [v for k, v in y]\n", + " print(x, y)\n", + " # host.plot(bar_x, y) \n", + " host.bar(bar_x, y, label=label) \n", + "#p2, = par.plot([0, 1, 2], [0, 3, 2], label=\"Temperature\", color=\"tab:red\")\n", + "data_overhead_tuf = [\n", + " 100.0 * np.sum(\n", + " np.average([dict_to_seconds(d.fetch_target_files) for d in vals])\n", + " + np.average([dict_to_seconds(d.update_repo) for d in vals])\n", + " ) / np.sum(\n", + " np.average([dict_to_seconds(d.fetch_target_files) for d in vals])\n", + " + np.average([dict_to_seconds(d.update_repo) for d in vals])\n", + " + np.average([dict_to_seconds(d.fetch_signature) for d in vals])\n", + " + np.average([dict_to_seconds(d.verify_binary) for d in vals])\n", + " + np.average([dict_to_seconds(d.fetch_binary) for d in vals])\n", + " )\n", + " for (k, vals) in data_by_size.items()]\n", + "data_overhead_no_tuf = [\n", + " 100.0 * np.sum(\n", + " np.average([dict_to_seconds(d.fetch_signature) for d in vals])\n", + " + np.average([dict_to_seconds(d.verify_binary) for d in vals])\n", + " ) / np.sum(\n", + " np.average([dict_to_seconds(d.fetch_target_files) for d in vals])\n", + " + np.average([dict_to_seconds(d.update_repo) for d in vals])\n", + " + np.average([dict_to_seconds(d.fetch_signature) for d in vals]) + np.average([dict_to_seconds(d.verify_binary) for d in vals])\n", + " + np.average([dict_to_seconds(d.fetch_binary) for d in vals])\n", + " )\n", + "for (k, vals) in data_by_size.items()]\n", + "host.legend(labelcolor=\"linecolor\")\n", + "par.plot(\n", + " bar_x,\n", + " data_overhead_no_tuf,\n", + " color=\"grey\", linestyle=\"--\",\n", + ")\n", + "par.plot(\n", + " bar_x,\n", + " data_overhead_tuf,\n", + " color=\"black\", linestyle=\"-.\",\n", + ")\n", + "# host.yaxis.get_label().set_color(p1.get_color())\n", + "#par.yaxis.get_label().set_color(p2.get_color())\n", + "par.set_ylim(0, 60)\n", + "host.set_ylim(0, 12)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Audit Level Comparison" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepare Data" + ] + }, + { + "cell_type": "code", + "execution_count": 454, + "metadata": {}, + "outputs": [], + "source": [ + "class BenchmarkData(BaseModel):\n", + " start_time: int\n", + " end_time: int\n", + " binary_size: int | None\n", + " fetch_target_files: dict\n", + " update_repo: dict\n", + " fetch_signature: dict\n", + " fetch_binary: dict\n", + " verify_binary: dict\n", + " verify_binary_naive: dict | None\n", + " \n", + "files = [f for f in os.listdir(\"../benchmark-output/benchmark-levels\") if f.startswith(\"performance-benchmark\")]\n", + "data = [BenchmarkData.parse_file(f\"../benchmark-output/benchmark-levels/{f}\") for f in files]\n", + "\n", + "data_fetch_targets = [dict_to_seconds(m.fetch_target_files) for m in data]\n", + "data_update_repo = [dict_to_seconds(m.update_repo) for m in data]\n", + "data_fetch_signature = [dict_to_seconds(m.fetch_signature) for m in data]\n", + "data_fetch_binary = [dict_to_seconds(m.fetch_binary) for m in data]\n", + "data_verify_binary = [dict_to_seconds(m.verify_binary) for m in data]\n", + "data_verify_binary_naive = [dict_to_seconds(m.verify_binary_naive) for m in data]\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot" + ] + }, + { + "cell_type": "code", + "execution_count": 455, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_14587/1789779907.py:2: MatplotlibDeprecationWarning: The 'labels' parameter of boxplot() has been renamed 'tick_labels' since Matplotlib 3.9; support for the old name will be dropped in 3.11.\n", + " ax.boxplot(\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "fig, ax = plt.subplots(1, 1)\n", + "ax.boxplot(\n", + " [\n", + " [sum(vals) for vals in product(data_fetch_binary)],\n", + " [sum(vals) for vals in product(data_fetch_binary, data_fetch_signature, data_verify_binary_naive)],\n", + " [sum(vals) for vals in product(data_fetch_binary, data_fetch_signature, data_verify_binary)],\n", + " [sum(vals) for vals in product(data_fetch_targets, data_update_repo, data_fetch_binary, data_fetch_signature, data_verify_binary)],\n", + " ],\n", + " labels=[\n", + " \"Level 0\",\n", + " \"Level 1 (no TUF)\",\n", + " \"Level 2 (no TUF)\",\n", + " \"Level 2\",\n", + " ],\n", + ")\n", + "ax.set_ylabel(\"Time (s)\")\n", + "# ax.set_title(\"Comparison of Different Audit Levels\")\n", + "ax.set_ylim(0, None)\n", + "fig.subplots_adjust(left=0.18, right=1, bottom=0.05, top=1, hspace=0.4, wspace=0.1)\n", + "fig.savefig(\"plots/benchmark-time-per-audit-level.png\", bbox_inches='tight', dpi=300)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/benchmarking/requirements.txt b/benchmarking/requirements.txt new file mode 100644 index 0000000..510faad --- /dev/null +++ b/benchmarking/requirements.txt @@ -0,0 +1,3 @@ +matplotlib +numpy +jupyter \ No newline at end of file diff --git a/benchmarking/size-benchmarks.ipynb b/benchmarking/size-benchmarks.ipynb new file mode 100644 index 0000000..efad468 --- /dev/null +++ b/benchmarking/size-benchmarks.ipynb @@ -0,0 +1,489 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 150, + "metadata": {}, + "outputs": [], + "source": [ + "#\"\"\n", + "# Apache License\n", + "# Version 2.0, January 2004\n", + "# http://www.apache.org/licenses/\n", + "#\n", + "# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n", + "#\n", + "# 1. Definitions.\n", + "#\n", + "# \"License\" shall mean the terms and conditions for use, reproduction,\n", + "# and distribution as defined by Sections 1 through 9 of this document.\n", + "#\n", + "# \"Licensor\" shall mean the copyright owner or entity authorized by\n", + "# the copyright owner that is granting the License.\n", + "#\n", + "# \"Legal Entity\" shall mean the union of the acting entity and all\n", + "# other entities that control, are controlled by, or are under common\n", + "# control with that entity. For the purposes of this definition,\n", + "# \"control\" means (i) the power, direct or indirect, to cause the\n", + "# direction or management of such entity, whether by contract or\n", + "# otherwise, or (ii) ownership of fifty percent (50%) or more of the\n", + "# outstanding shares, or (iii) beneficial ownership of such entity.\n", + "#\n", + "# \"You\" (or \"Your\") shall mean an individual or Legal Entity\n", + "# exercising permissions granted by this License.\n", + "#\n", + "# \"Source\" form shall mean the preferred form for making modifications,\n", + "# including but not limited to software source code, documentation\n", + "# source, and configuration files.\n", + "#\n", + "# \"Object\" form shall mean any form resulting from mechanical\n", + "# transformation or translation of a Source form, including but\n", + "# not limited to compiled object code, generated documentation,\n", + "# and conversions to other media types.\n", + "#\n", + "# \"Work\" shall mean the work of authorship, whether in Source or\n", + "# Object form, made available under the License, as indicated by a\n", + "# copyright notice that is included in or attached to the work\n", + "# (an example is provided in the Appendix below).\n", + "#\n", + "# \"Derivative Works\" shall mean any work, whether in Source or Object\n", + "# form, that is based on (or derived from) the Work and for which the\n", + "# editorial revisions, annotations, elaborations, or other modifications\n", + "# represent, as a whole, an original work of authorship. For the purposes\n", + "# of this License, Derivative Works shall not include works that remain\n", + "# separable from, or merely link (or bind by name) to the interfaces of,\n", + "# the Work and Derivative Works thereof.\n", + "#\n", + "# \"Contribution\" shall mean any work of authorship, including\n", + "# the original version of the Work and any modifications or additions\n", + "# to that Work or Derivative Works thereof, that is intentionally\n", + "# submitted to Licensor for inclusion in the Work by the copyright owner\n", + "# or by an individual or Legal Entity authorized to submit on behalf of\n", + "# the copyright owner. For the purposes of this definition, \"submitted\"\n", + "# means any form of electronic, verbal, or written communication sent\n", + "# to the Licensor or its representatives, including but not limited to\n", + "# communication on electronic mailing lists, source code control systems,\n", + "# and issue tracking systems that are managed by, or on behalf of, the\n", + "# Licensor for the purpose of discussing and improving the Work, but\n", + "# excluding communication that is conspicuously marked or otherwise\n", + "# designated in writing by the copyright owner as \"Not a Contribution.\"\n", + "#\n", + "# \"Contributor\" shall mean Licensor and any individual or Legal Entity\n", + "# on behalf of whom a Contribution has been received by Licensor and\n", + "# subsequently incorporated within the Work.\n", + "#\n", + "# 2. Grant of Copyright License. Subject to the terms and conditions of\n", + "# this License, each Contributor hereby grants to You a perpetual,\n", + "# worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n", + "# copyright license to reproduce, prepare Derivative Works of,\n", + "# publicly display, publicly perform, sublicense, and distribute the\n", + "# Work and such Derivative Works in Source or Object form.\n", + "#\n", + "# 3. Grant of Patent License. Subject to the terms and conditions of\n", + "# this License, each Contributor hereby grants to You a perpetual,\n", + "# worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n", + "# (except as stated in this section) patent license to make, have made,\n", + "# use, offer to sell, sell, import, and otherwise transfer the Work,\n", + "# where such license applies only to those patent claims licensable\n", + "# by such Contributor that are necessarily infringed by their\n", + "# Contribution(s) alone or by combination of their Contribution(s)\n", + "# with the Work to which such Contribution(s) was submitted. If You\n", + "# institute patent litigation against any entity (including a\n", + "# cross-claim or counterclaim in a lawsuit) alleging that the Work\n", + "# or a Contribution incorporated within the Work constitutes direct\n", + "# or contributory patent infringement, then any patent licenses\n", + "# granted to You under this License for that Work shall terminate\n", + "# as of the date such litigation is filed.\n", + "#\n", + "# 4. Redistribution. You may reproduce and distribute copies of the\n", + "# Work or Derivative Works thereof in any medium, with or without\n", + "# modifications, and in Source or Object form, provided that You\n", + "# meet the following conditions:\n", + "#\n", + "# (a) You must give any other recipients of the Work or\n", + "# Derivative Works a copy of this License; and\n", + "#\n", + "# (b) You must cause any modified files to carry prominent notices\n", + "# stating that You changed the files; and\n", + "#\n", + "# (c) You must retain, in the Source form of any Derivative Works\n", + "# that You distribute, all copyright, patent, trademark, and\n", + "# attribution notices from the Source form of the Work,\n", + "# excluding those notices that do not pertain to any part of\n", + "# the Derivative Works; and\n", + "#\n", + "# (d) If the Work includes a \"NOTICE\" text file as part of its\n", + "# distribution, then any Derivative Works that You distribute must\n", + "# include a readable copy of the attribution notices contained\n", + "# within such NOTICE file, excluding those notices that do not\n", + "# pertain to any part of the Derivative Works, in at least one\n", + "# of the following places: within a NOTICE text file distributed\n", + "# as part of the Derivative Works; within the Source form or\n", + "# documentation, if provided along with the Derivative Works; or,\n", + "# within a display generated by the Derivative Works, if and\n", + "# wherever such third-party notices normally appear. The contents\n", + "# of the NOTICE file are for informational purposes only and\n", + "# do not modify the License. You may add Your own attribution\n", + "# notices within Derivative Works that You distribute, alongside\n", + "# or as an addendum to the NOTICE text from the Work, provided\n", + "# that such additional attribution notices cannot be construed\n", + "# as modifying the License.\n", + "#\n", + "# You may add Your own copyright statement to Your modifications and\n", + "# may provide additional or different license terms and conditions\n", + "# for use, reproduction, or distribution of Your modifications, or\n", + "# for any such Derivative Works as a whole, provided Your use,\n", + "# reproduction, and distribution of the Work otherwise complies with\n", + "# the conditions stated in this License.\n", + "#\n", + "# 5. Submission of Contributions. Unless You explicitly state otherwise,\n", + "# any Contribution intentionally submitted for inclusion in the Work\n", + "# by You to the Licensor shall be under the terms and conditions of\n", + "# this License, without any additional terms or conditions.\n", + "# Notwithstanding the above, nothing herein shall supersede or modify\n", + "# the terms of any separate license agreement you may have executed\n", + "# with Licensor regarding such Contributions.\n", + "#\n", + "# 6. Trademarks. This License does not grant permission to use the trade\n", + "# names, trademarks, service marks, or product names of the Licensor,\n", + "# except as required for reasonable and customary use in describing the\n", + "# origin of the Work and reproducing the content of the NOTICE file.\n", + "#\n", + "# 7. Disclaimer of Warranty. Unless required by applicable law or\n", + "# agreed to in writing, Licensor provides the Work (and each\n", + "# Contributor provides its Contributions) on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n", + "# implied, including, without limitation, any warranties or conditions\n", + "# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n", + "# PARTICULAR PURPOSE. You are solely responsible for determining the\n", + "# appropriateness of using or redistributing the Work and assume any\n", + "# risks associated with Your exercise of permissions under this License.\n", + "#\n", + "# 8. Limitation of Liability. In no event and under no legal theory,\n", + "# whether in tort (including negligence), contract, or otherwise,\n", + "# unless required by applicable law (such as deliberate and grossly\n", + "# negligent acts) or agreed to in writing, shall any Contributor be\n", + "# liable to You for damages, including any direct, indirect, special,\n", + "# incidental, or consequential damages of any character arising as a\n", + "# result of this License or out of the use or inability to use the\n", + "# Work (including but not limited to damages for loss of goodwill,\n", + "# work stoppage, computer failure or malfunction, or any and all\n", + "# other commercial damages or losses), even if such Contributor\n", + "# has been advised of the possibility of such damages.\n", + "#\n", + "# 9. Accepting Warranty or Additional Liability. While redistributing\n", + "# the Work or Derivative Works thereof, You may choose to offer,\n", + "# and charge a fee for, acceptance of support, warranty, indemnity,\n", + "# or other liability obligations and/or rights consistent with this\n", + "# License. However, in accepting such obligations, You may act only\n", + "# on Your own behalf and on Your sole responsibility, not on behalf\n", + "# of any other Contributor, and only if You agree to indemnify,\n", + "# defend, and hold each Contributor harmless for any liability\n", + "# incurred by, or claims asserted against, such Contributor by reason\n", + "# of your accepting any such warranty or additional liability.\n", + "#\n", + "# END OF TERMS AND CONDITIONS\n", + "#\n", + "# APPENDIX: How to apply the Apache License to your work.\n", + "#\n", + "# To apply the Apache License to your work, attach the following\n", + "# boilerplate notice, with the fields enclosed by brackets \"[]\"\n", + "# replaced with your own identifying information. (Don't include\n", + "# the brackets!) The text should be enclosed in the appropriate\n", + "# comment syntax for the file format. We also recommend that a\n", + "# file or class name and description of purpose be included on the\n", + "# same \"printed page\" as the copyright notice for easier\n", + "# identification within third-party archives.\n", + "#\n", + "# Copyright 2024 Fraunhofer AISEC\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "import os\n", + "import matplotlib.pyplot as plt\n", + "import json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Libs that are dependencies of both TUF and BT code\n", + "\n", + "The folowing libraries are mostly used by both and therefore cannot be assigned as dependencies for one of the specific features. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 151, + "metadata": {}, + "outputs": [], + "source": [ + "crypto_libs = [\n", + " \"p256\",\n", + " \"sha2\",\n", + " \"pem_rfc7468\",\n", + " \"spki\",\n", + " \"pkcs8\",\n", + " \"der\",\n", + " \"der?\",\n", + " \"x509_cert\",\n", + " \"serde_json\",\n", + " \"const_oid\",\n", + " \"ecdsa\",\n", + " \"base64ct\",\n", + " \"sec1\",\n", + " \"crypto_bigint\",\n", + " \"heapless\",\n", + " \"smoltcp\",\n", + " \"embassy_net\",\n", + " \"fixed\",\n", + " \"hash32\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Flash Size and WiFi Drivers\n", + "\n", + "- The flash size is listed as 2MB, which we convert to MiB\n", + "- The drivers for the WiFi chip on the Pi Pico W requires drivers that need to flashed onto the same flash chip as the binary itself. It can also be \"baked\" into the binary directly, however this makes it impossible to reuse the wifi drivers that are already stored on the chip for new binaries when updating. It also increases the size of the binaries significantly." + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "metadata": {}, + "outputs": [], + "source": [ + "wifi_driver_size = os.stat(f\"../-pi-pico-w/firmware/43439A0_clm.bin\").st_size + os.stat(\"../-pi-pico-w/firmware/43439A0.bin\").st_size\n", + "wifi_driver_size /= 1024 # scale to KiB\n", + "\n", + "flash_size = 2 * 1e6 / 1024**1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Colors" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "metadata": {}, + "outputs": [], + "source": [ + "flash_size_data_color = \"tab:blue\"\n", + "wifi_driver_size_data_color = \"tab:red\"\n", + "file_size_data_color = \"tab:olive\"\n", + "file_text_section_color =\"tab:pink\"\n", + "tuf_no_std_size_color = \"tab:cyan\"\n", + "_size_color=\"tab:orange\"\n", + "libs_size_color=\"tab:purple\"" + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "processing size benchmarks\n", + "[('benchmark-code-size-full', 260.671875), ('benchmark-code-size-no-tuf', 189.7578125), ('benchmark-code-size-signature-only', 144.08984375), ('benchmark-code-size-update-only', 101.12890625)] [('benchmark-code-size-full', 484.248046875), ('benchmark-code-size-no-tuf', 413.333984375), ('benchmark-code-size-signature-only', 367.666015625), ('benchmark-code-size-update-only', 324.705078125)]\n" + ] + } + ], + "source": [ + "data_path = \"../benchmark-output/benchmarks-size.json\"\n", + "\n", + "plt.style.use(\"Solarize_Light2\")\n", + "print(\"processing size benchmarks\")\n", + "with open(data_path) as f:\n", + " raw_data = json.load(f)\n", + "\n", + "\n", + "file_size_data = [(name, data[\"raw_size\"]/(1024**1)) for (name, data) in raw_data.items()]\n", + "flash_size_data = [(name, flash_size) for (name, _) in raw_data.items()]\n", + "file_text_section = [(name, data[\"cargo_bloat_crates\"][\"text-section-size\"]/(1024**1)) for (name,data) in raw_data.items()]\n", + "tuf_no_std_size = []\n", + "_size = []\n", + "libs_size = []\n", + "for (name, data) in raw_data.items():\n", + " crates = data[\"cargo_bloat_crates\"][\"crates\"]\n", + " size_tuf_no_std = sum(c[\"size\"] for c in crates if c[\"name\"].startswith(\"tuf_no_std\")) / (1024**1)\n", + " size_ = sum(c[\"size\"] for c in crates if c[\"name\"].startswith(\"\")) / (1024**1)\n", + " size_libs = sum(c[\"size\"] for c in crates if any(c[\"name\"].startswith(l) for l in crypto_libs)) / (1024**1)\n", + " libs_size.append((name, size_libs, size_libs))\n", + " _size.append((name, size_ + size_libs, size_))\n", + " tuf_no_std_size.append((name, size_tuf_no_std + size_ + size_libs, size_tuf_no_std))\n", + "wifi_driver_size_data = [(name, data + wifi_driver_size) for (name, data) in file_size_data]\n", + "print(file_size_data, wifi_driver_size_data)\n", + "tuf_no_std_size = list(zip(*tuf_no_std_size))\n", + "_size = list(zip(*_size))\n", + "libs_size = list(zip(*libs_size))\n", + "\n", + "table_data = {\n", + " \"Entire Binary\": [f\"{v:.2f} KiB\" for (_, v) in file_size_data],\n", + " \".text section\": [f\"{v:.2f} KiB\" for (_, v) in file_text_section],\n", + " \"TUF\": [f\"{v:.2f} KiB\" for v in tuf_no_std_size[2]],\n", + " \"BT²X\": [f\"{v:.2f} KiB\" for v in _size[2]],\n", + " \"Libs\": [f\"{v:.2f} KiB\" for v in libs_size[2]],\n", + "}\n" + ] + }, + { + "cell_type": "code", + "execution_count": 155, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "x_labels = [\n", + " \"audit level 2\",\n", + " \"audit level 2 (no TUF)\",\n", + " \"audit level 1 (no TUF)\",\n", + " \"audit level 0\",\n", + "]\n", + "ax.set_ylabel(\"Size (KiB)\")\n", + "ax.bar(*zip(*flash_size_data), label=\"Total Flash\", width=0.85, hatch=\"*\", color=flash_size_data_color, align=\"edge\")\n", + "ax.bar(*zip(*wifi_driver_size_data), label=\"WiFi Drivers\", width=0.8, hatch=\"//\", color=wifi_driver_size_data_color, align=\"edge\")\n", + "ax.bar(*zip(*file_size_data), label=\"Entire Binary\", width=0.8, color=file_size_data_color, align=\"edge\")\n", + "ax.bar(*zip(*file_text_section), label=\".text section\", width=0.75, hatch=\"o\", color=file_text_section_color, align=\"edge\")\n", + "ax.bar(tuf_no_std_size[0], tuf_no_std_size[1], label=\"TUF\", width=0.7, hatch=\"+\", color=tuf_no_std_size_color, align=\"edge\")\n", + "ax.bar(_size[0], _size[1], label=\"BT²X\", width=0.7, hatch=\"//\", color=_size_color, align=\"edge\")\n", + "ax.bar(libs_size[0], libs_size[1], label=\"Libs\", width=0.7, hatch=\"\\\\\\\\\", color=libs_size_color, align=\"edge\")\n", + "ax.legend(loc=\"upper right\")\n", + "\n", + "# table = plt.table(\n", + "# colWidths=[0.25, 0.25, 0.25, 0.25],\n", + "# cellText=list(table_data.values()),\n", + "# rowLabels=list(table_data.keys()),\n", + "# colLabels=list(raw_data.keys()),\n", + "# )\n", + "# table.auto_set_font_size(False)\n", + "# table.set_fontsize(8)\n", + "plt.subplots_adjust(left=0.2, bottom=0.2)\n", + "x_tick_distances = [1*v for v in [0.445, 0.445 + 1*(0.85 + 2*0.075), 0.445 + 2*(0.85 + 2*0.075), 0.445 + 3*(0.85 + 2*0.075)]]\n", + "ax.set_xticks(x_tick_distances, labels=x_labels)\n", + "fig.subplots_adjust(left=0.18, right=2/1.5, bottom=0.05, top=1.5/1.6, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-size-total-storage.pdf\", bbox_inches='tight')\n" + ] + }, + { + "cell_type": "code", + "execution_count": 156, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAhsAAAFiCAYAAABf4hopAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAAsTAAALEwEAmpwYAABkxklEQVR4nO3dd1hT1/8H8HdARLYKOOveG21lQ8IQB6KiraPOuqp+nd9a/VVrW63bOqpWraNaV6tWrVq3VlTco7jqQAUHKg72kJXz+4NvYkJuWJLcc8Pn9Tx9npp8cu/NOeHm5I7zluWkPWEghBBCCDEQM7E3gBBCCCGmjQYbhBBCCDEoGmwQQgghxKBosEEIIYQQg6LBBiGEEEIMigYbhBBCCDGoMmJvAK8Sk5LE3gRCCCFEMmQA7O3tBZ+jIxuEEEIIeW9mZvqHFDTYIIQQQohB0WCDEEIIIQZFgw1CCCGEGBRdIEoIIYRrOTkMbxKzkJWlBIV5iUsGwMLCDI4OFjA3lxX6dTTYIIQQwrU3iVmwsjSDcwULyGSF/4IjJY8xhuS0HLxJzEKlimUL/To6jUIIIYRrWVlK2Fqb00CDAzKZDHbW5sjKUhbpdTTYIIQQwjUG0ECDIzKZrMins+g0CiGEEFIAp2ot0LRJA/W/u3friPFjhumtDz9zERZlLeDWtjUAYP2v22BlVQ69e3Yt1vrnLvgJm7b8AUfHCsjIyIS3pysWzP0aZmZmmD1vGTw9PoLC16NYyzYGGmwQQgiRlKu3Qkp0eW2a7SuwxqqcJU4d31XoZYafvQQbG2v1YOOzgb0E67Kzs1GmTOG+ikcMH4Axoz6DUqlEcLcBOHP2Eny83TBl8phCb1d+irItRUWDDUIIIaSYWn3UDr17dsXho2HIysrG+jWLUM7SEhs2boO5uTl27NyHebOm4uTp87CxscaYUZ8hJHQQmjdvhAsX/kH30E7w9myLr7+dj9S0NFSsWB4//TgbVSo7611nZmYWMt5monz53KnB/zN2CoLaydE1pL3g9jRsUBdXrl7HV9PmIiMjA+XKlcPyJTPRoH4dbP19N/46cAypqWnIyVGixgfV0Dk4EMEdAwAAw0dNQrcuHdCpg/97tRMNNoyopEfjRFthfp0QQkhxpL/NgG9Ad/W/x48Zhu7dOgIAHCtWQNjRP7Bu/W9YvnIDli6agUEDeqkHFwBw8vR5reVlZWbh7yPbkZWVhc6hg7BlwzI4OVXErj8PYuacH7F8yUydbVi1eiN27NyHJ0+fI9DfGy2aNxHcVqHtadCgLg7s2YgyZcog7NQ5fD9nCTau+xEAcO3GbYT/vQsVKpTHmbOXsHL1RgR3DEBSUjIuXorAiqWz37v9aLBBCCGEFCC/0yidgwMBAK1aNcNfB44VanmhXXMHKpH3o3H7TiS69xoKAMjJUaKynqMaqtMoWVlZGDR0Anb+eQA9unUq1PYkJSXjP2On4MHDR5DJZMjOzlbXK3w9UKFCeQCAl2dbTPy/7/H6dRz27T+KkOB2JXJqhQYbhBBCyHuwLJs734S5mRmys3MK9Rpra6v//R9D40b1cWT/1kKvz8LCAgF+3jh37rLgYENoe+bMWwZvL1dsWr8Ujx/HIKT7IHW9jXpbcvX+pAu279yHXX8eFDzCUhx06yshhBBSwmxtbZCSklpgXf16tfHmTRwuXo4AAGRlZeH2nfv5voYxhguX/kHt2jULvT1JySmoWqUSAGDrtj/zre3TuxtWrd4EAGjcqH6h15EfGmwQQgghBVBds6H6b/rMRfnWdwhSYP/B4/AN6I5z56/orStbtiw2rF2M6TMXwcc/FL4BPXDx8j+CtatWb4RvQHd4yrsiJycHQwb1LvT2j/nPYHw/ewnkgT2Qk5Odb20lZyc0bFgXfXuHFnr5BZHlpD2hqeYFJCYllfgy6QJRw6ILRAkxTU9j3+KDyuXE3oxSIy0tHd5+oQg7ugP29naCNUJ9Ym5mBltbW8F6yV2zEZeUig17TyMpNR0yyODduiECXJti36l/EP5PJOysLQEAXf0+RIv6HwAADp25jjPXImEmk6FnkBua1asu5lsghBBCuBR26hzGTpiGkZ8P0DvQKA7JDTbMZTJ8HNAWNas64m1GFmb/sg9N6lQDAAS4NUWQe3Ot+mevEnDp3yh8M7wbElPSsGTLEcwYGQozMzqDRAghhGhS+Hrg+pXC3VFTFJL7xnWws0bNqo4AgHKWFqji6ICE5DS99dfvPUbbpnVgUcYcTuXtUKmiHaKfvTbW5hJCCCGlnuQGG5peJyTjSWwc6lR3AgCEXb6N79fswcZ94UhNzwAAxCenoYK9jfo15e1sEJ/P4IQQQgghJUtyp1FU3mZmYfXOMPRs5wory7KQt2mMYO9WgEyGvWH/YOexSxgQ4l2kZZ6+ehen/7kHABjU2QPVKjsZYtOJgTjY24u9CYQQA3j2KhPmdOqbK2ZmZjr73JSUFL31khxs5OQosXrnCbg2r4vWjWsBAOxt301K4t26AVZsPw4AqGBnjfikd/c6JySnooKdteByfdo0gk+bRgBy70YxxB0pxHCovwgxTUqlEjlKpdibQTQolUqdfW5+A0LJDRUZY9i4/wyqODog0K2Z+vFEjVMjEXcfo5pzeQBAy4Y1cOnfKGRl5+B1QjJexiWhdjU6YkEIIaR4Vq7eiLS09GK9NjExCevW/1bCW/TO48cx+GPXX+p//xNxE/839f2zTd6X5I5sPHj6EhduPED1ShUwc80eALm3uV6+9RBPYuMgk8ng6GCLvh09AADVnCvgwya1Mf3nP2FuJkPv9u50JwohhEhY6uI3Jbo8mwmORapftXoTevYI0ZhyvPASE5OxbsPvGPJZnyK/tjAeP4nBH7sO4OPunQEArV2ao7VL8wJeZXg0qZceNKmX9NCkXoSYprwTSAkNNpRMiWG7v8aRyHAAgLyOKzZ+Mh9mMuEfl5r1cS9uFXpbfl67Gd9MX4D69erAsWJ57N21AX+HncHcBT8hMzMTtWvVwPIfZyI+LhGhPYfg8F9bUaGCAzqHDsTECSOw5bfdOHj4b9SvVxsKX0/M+Hbiu/eVmobBw7/As+cvkJOjxMQJI9C9W0dEXLslGEH/MOoRvpg0A6/fxMHc3Bzr1yzC8FGTcS/yIWrVrI7ePbuiZfMmWL5yA37fvALx8QkYM2Eaoh89hZVVOSz54Ts0a9oIcxf8hKcxz/Ho0VM8jXmOEcP74/Oh/YrUJ4CJTepFCCGE5DVgxyScjLqIj5t3AAD8cfMQhu+ehrXdZxWqvrA+H9oPK1b9ir0718PRsQLevInHwiU/Y/f2tbCxscaPy9ZixapfMemLURg7egi+mDwDbVq3QKOG9eCv8EL9urVx+06kYILs8RPhqFLFGdu2rASQm9SalZWFyVNnC0bQDx81GePHDEXnToF4+zYDSqUS306doB5cAED4mYvq5c9d8BNaNG+CzRuW4VT4eYwc85V6OyLvR2HvzvVISUmFq3cwBg/sBQsLiyK1TX5osEEIIUTyVAOHxcFT1I/9cfNQkeqL4/KVa7h77wE6dsk9EpCZmYW2H7kAAAb0/Rh79h3Gho3bcfL4zgKX1bRJQ0z7bgG++34h2rdTwMP9Q/x7O1Iwgj45JRXPX7xE5065cfLlylkWuPzzF6/i13VLAAC+3u6Ii09EUnLuHSRBgb6wtCwLS8uycHJyxMtXb1C9WpWiNodeNNgghBBCiomBQeHrgbWrftB5Li0tHc+exQLIPUViZ2ujU6Opfr3aCDu6A0ePn8asuUvh6+OGzp0CBSPokwuRKFsUZf8XSw/kng7J+V80fUmhKyUJIYRIXlADb/xx8xAm7J+NCftn44+bh6Co41ao+qLSjI//qE0rXLj0Dx5GPQKQO6i4/yAaADB95iJ80qMzvpo0GuO/+FbntXk9f/ESVlZW6PlxCMaM+gzXb9zWG0FvZ2uDalUrY//B3GkeMjIykZaWnu/yPdw+xI6duXeqhJ+5CMeK5WFvJ3yNRUmjwQYhhBDJWxM6E4o6bvjj5iH8cfMQ2jfwwa+fzCtUfWH0/HQEnr94CQAY2O8TfPLp5+jSfRCcnCripx9nYdiIL+HtF4r2nfsi8v5DnDl7CVcjbmLc6CH4pEdnlC1rgS2/7UbFiuXh5toanvKu+Ga69tGQf2/fQ2DH3vAN6I75C1fgi/Gf5xtBv2r5XKxeuxnefqHoENIXL1+9RrOmDWFubgYf/1Cs+PlXreVPnjgK167/C2+/UEyftRgrlhrvlli6G0UPuhtFeuhuFEJME0XM86eod6PQkQ1CCCGEGBQNNgghhBBiUDTYIIQQQohB0WCDEEIIIQZFgw1CCCGEGBRN6mVEjY5sAPB+c/hTfa67QYMEX0sIIYQ/dGRDBAN2TMKRyHB83LwDPm7eASejLmL47mlUX8x6QggxpLi4BPgGdIdvQHc0buGLZi5+8A3ojtoN3eHuoz2lwdwFP2HZivUAgP+MnQKXtkHq1/68drMYm88FOrIhgpKYw5/qCSGlVZN/M/U+l7JhFQDAdtCIQi0rZcMqPJk/Nt+aihXLqwPL5i74CTY21hgz6jM8fhyD3v1H5fva6d98ga4h7Qu1LaaMjmwQQgghxKDoyIYIVHPyqxR2Dn+qJ4QQafl2xkIsXPIzgNzpxZs2aSjyFomDBhsiWBM6EwN3TFZ/obZv4IPVod9TfTHrCSFEDDKZrMDH6TRKLhpsiMBMZoZNPRdQfQnVE0KIGCpUdEBionaOVkJCImrVrC7SFvGLrtkghBBCisHWxgaVKznjVPh5AEB8fAKOnwiHu1sbkbeMP3RkgxBCiKSo7jgRkrrx5yItK3Xjz0ABd6PkZ8Wy2Zj01Ux8/e18AMCkL0ahTu2axV6eqaKIeT0METGfuvhNiS+ztBKa1Isi5gkxTRQxzx+KmCeEEEIIV2iwQQghhBCDoms2jKjf9oncZ45Ipf5YUF3BGkIIIfyhIxtGJHaGiCnVE0IIkQ46smFEHzfvIKnMEZ7r72KQ3jpCCCF8oSMbhBBCCDEoOrJhRGJniJhS/YggvWWEEFLinKq1QNMmDcAYg7m5OebNngo7WxuMGP1/AICnMc9hb2cHe3tbOFasgD69uuHCxX/w+EkMVq+Yh+hHTzFq7BScPPYHypYti6jox+jecxhOHt8Jezvh20VNCc2zoYch5tno4zMCq0O/z/cCyIE7JiMs6gKAd5kgVK9bf+y47gWiNM8GIaYp75wO1ya3haJ2wb+Vw6Kz8cmOdOz4xCrf+viR5wpcVo26H+HJw8sAgOMnwrH4xzX4689f1c//Z+wUBLWT6+SgjBj9f5g2ZTyqV6uCiZNnoFq1KvjvuOH4uM9w9OnZFT1CgwtcN4+KOs8GHdkworXdZ+X7PG+ZIzzX0zUbhJReJTnQKI7k5FSUL2+fb41SqcTsecvwcfdgVK9WBQAwbcp4yAM/Rhlzc+Rk50h2oFEcNNgghBBiUgwx0Eh/mwHfgO7IyMjEi9hX2PPHL/nWfzP9B1y49A9evnqD6tWqoknj+nBwsMf4MUMx8f++x7lTe0tku6SCBhuEEEJMRlEHGmHR2WhViOValbPEqeO7AAAXL0dg5JivcPbkHr0x8zOnTxJ8/Njfp1HJ2RF37z1Ag/p1CrFm00B3oxBCCDEJxRlofLIjvcjrcf3IBXFxCXj9Oq5Irzt8JAxJScn44/fV+HbGD0hLK/q6pYoGG4QQQiSvuAONHZ9YFXld9yIfIkeZg4oVyxf6Nenpb/H1d/Mxf87XaNqkITq298eiH1cXed1SRadRCCGESNr7DDQUtcsgvhDrUF2zAQCMMaz4cTbMzc0LvY0/LF6F4I4BaNyoPgBg8pf/ga9/d/Tp1Q316tYq9HKkim591cOQEfM8Z45IpZ4i5gkpPShinj8UMS8BPGeOSLGeEEII3+g0igh4zhyRYj0hhBC+SW6wEZeUig17TyMpNR0yyODduiECXJsiNT0Da3aH4U1CChzL22JYqAI2VpZgjGH7kYu4+eApylqUwcDO3qhZ1VHst0EIIYSUGpIbbJjLZPg4oC1qVnXE24wszP5lH5rUqYZz1++jce2q6ODZEofOXsfhczfQ3f8j3HwQg5dxSZgxsjuinr3C1kPn8H+fdRb1PfCcOSLFekIIIXyT/AWiK7Yfh+KjJth2+Dz+268DHOyskZichkWbD2H6yO7YcuAsGtaqgrbNcrM0vl25S12XH0NfIMpr5ohU6ukCUUJKD7pAlD+lKhvldUIynsTGoU51JySlpqsHEPa2VkhKzZ0sJSE5DRXsbdSvKW9vg4TkNMHBxumrd3H6n3sAgEGdPVCtslOJbm8qcgcbPGeOSLFexcE+/6wCQog0PXuVCXMzup+BJ2ZmZjr73JSUFL31kh1svM3MwuqdYejZzhVWlmW1npPJZHqnkM2PT5tG8GnTCEDukQ1DHN0ghkP9RYhpUiqVyFEqRd0GzdRXlfW/boOVVTn07tkVIaGDMOPbiWjt0lykLTQupVKps8/Nb0AoycFGTo4Sq3eegGvzumjdOHcyFHsbKyT+74hFYnIa7KxzD++Ut7NGfFKq+rUJSakoX8ApFEIIIfza+u0LwcfvPYvAL0dnYHC7b9CwmkuBy1HVP44+W6zt+Gxgr2K9rjSS3HEpxhg27j+DKo4OCHRrpn68ZcMaOHfjPgDg3I37aNmwZu7jDWrg/PUHYIzhYcxLlLMsW+D1GoQQQqSnYTUXDG73DX45OgP3nkUUur645i74CctWrFf/e9sf++Ab0B2e8q64cvU6AODM2UvwDegO34DukAf2QHJKqr7FmTTJHdl48PQlLtx4gOqVKmDmmj0AgK5+H6K9Rwus2X0SZyIi4ehgi2HdFQCA5vU/wM0HMZi2YhfKWphjYGdvEbeeEEKIIWkOOApzhKMwR0AKKz09HaeO78LZc5cxZsI0nD25B8tXrsf8OV/D3bUNUlJTUc7SssTWJyWSG2zUr1EZq6YOEnxuQt/2Oo/JZDL06eBu4K0ihBDCi6IOOEpKj9BOAABPj4+QnJKCxMQkuLm2xtffzscnPTqjc6dA2FazKWAppklyp1GkrN/2iVAy/Rc5KZkSQ3ZNQY15vqgxz5fq86knhJReRTlFUthTKiVBBu0bE2QyGcaPGYali2bgbfpbdAzph3uRD42yLbyhwYYRiZ0hYkr1hJDSq6jXZBhrwLF7b+5khOcvXIG9nR3s7e0QFf0YTZs0xLgxQ9HapTki70cZfDt4JLnTKFL2cfMOksoc4bn+LgbprSOEmLaiXpNREqdU0tLfollrf/W/R30+QKfG0rIs5IE9kJWVjWWLvwcArFq9CafPXISZmRkaN6qHQH+fYq1f6miwQQghRFK+W9MB36EoRziLWq/rzfOb+T6/b/cGwcfnzZ76Xus1FTTYMCKxM0RMqX5EkN6yEnf1VojxVlZK0VTzhJg2yWejGIohZqPs4zOC+8wRqdQfO15X53lDfWHRYMPwaLBB8kPZKPwpajYKDTb0MGQQG3l/xgxio8GG4dFgg+SHBhv8Kepgg+5GIYQQwjUZcmePJnxgjKGo6WM02CCEEMI1CwszJKfl0ICDA4wxJKflwMKiaMMHukCUEEII1xwdLPAmMQvJKdmg4Ya4ZMgd/Dk6WBTpdTTYIIQQwjVzcxkqVSwr9maQ90CnUQghhBBiUHRkQwRKpsSw3V/jSGQ4AEBexxUbP5mf7y2fVK+/nhBCCN9o7y0CnjNHpFhPCCGEb3RkQwQ8Z45IsZ4QQgjf6MgGIYQQQgyKjmyIgOfMESnWE0II4RtNV66HIacr5zlzRCr1NF25aaHpygmRPspGKQbKRuEbDTZMCw02CJE+ykYhhBBCiGhosEEIIYQQg6LBBiGEEEIMigYbhBBCCDEoGmwQQgghxKBong0j6rd9oqQyR3iuPxZUV7CGEEIIf+jIhhGJnSFiSvWEEEKkg45sGNHHzTtIKnOE5/q7GKS3jhBCCF9osEFIARod2SD4eI15vlqDpQn7Z+OPm4fwZPIpqtdTP+LHe4K1hBDTZtTBRkraW5y/8QA37j/F09g4pGdkwsqyLD6oXBHN6lWHR4v6sLMpZ8xNMiqxM0RMqX5EkN4yo+G5fXitHwG61oaQ0sho05Xv/vsyLt58iOb1P0CDmlVQxckB5cpa4G1mFl68TkTk4xe4ef8pXJvXRaj/R8bYpHwZYrryPj4juM8ckUr9seO6X1qGmvJa3zTzPLcPr/VfrooRrKXpygmRPi6yUU5cug3v1g1hUcZcb01WdjbC/4mEX9smxtikfFE2Ct+MmY1C/VZyhPoNoMEGIaYgv8GG0U6jFGYAYVGmDBcDDUIIIYSUHNEvEI15GY/bUc9Q1ak8mtWrLvbmEEIIIaSEGXWwEfMyHhv2nsbz1wmoU90ZnX1c8PPOE3CuYI8XbxLQVdEG/m2bGnOTCCGEEGJgRh1s/H74PFo2qIHB3Xxx4cZD/LzzBEb1DED9GpXx8OlLbNgXToMNQgghxMQYdQbRmJfx6OzrgqpO5dHZtxUys7JRv0ZlAEDdDyohKSXdmJtDCCGEECMw6pENpZJBJpPlrtjcHGUtRL9kRBQ8Z45Isd7QeHu/Uq8nhJQ+Rrv1FQDGzNuEPh3cwf63xu1HLqBn0LtJgX4/fB7LJvc31ubkyxC3vjb5N7NQdSkbVgEAbAeNKPX1PmO/wh83D6F9Ax+s7T5L/bgxb30N9fhMPVU6AMHt0dRv+0Sq11NPt74SYrq4uPUVAGpXc8L5Gw/U/65VzQkXbr77d53qzoVazsZ94bhx/ynsbMrhm+HdAAD7Tv2D8H8iYWdtCQDo6vchWtT/AABw6Mx1nLkWCTOZDD2D3OiuFwlRTXWdX2aKofGcESPFekJI6WPUwcYX/TuWyHI8WtWH4qMm2LDvtNbjAW5NEeTeXOuxZ68ScOnfKHwzvBsSU9KwZMsRzBgZCjMzOsRLCCGEGIPRBhuMvbteQ8n0n7kx+19NfhrUrILXCcmFWu/1e4/RtmkdWJQxh1N5O1SqaIfoZ69R94NKhdtwIipV2Fd+GRyGJoXMESnVE0JKH6MNNib8sBVLvuwLAPjP7F+BvIMKxgCZDCunDCz2OsIu38aFGw9Qq4ojegS2hY2VJeKT01BX4/RMeTsbxCenCb7+9NW7OP1PbirloM4eqFbZqdjbIux1CS/P9KnO/68O/b7AWgd7e4Nsw5rQmRi4Y7L6C7Wg7aH6wvWXJkP1HSHEeFJSUvQ+Z7TBxjefd1P//8zRH5f48uVtGiPYuxUgk2Fv2D/YeewSBoR4F2kZPm0awadNIwC5F4ga4iJRUjT64suFGKq/zGRm2NRzAdWXUL0Q+lsjRPrM87k8wWgXLlS0t0Hk41gAgKODreB/p6/eK/by7W2tYGZmBjOZDN6tGyD6ee5RhAp21ohPSlXXJSSnooKd9fu9GUIIIYQUmlEvEF254zjG9G4neNfJjqMX8c+dR+jm16ZYy05MToPD/wYREXcfo5pzeQBAy4Y1sO7PUwhwa4bElDS8jEtC7WolfXqkcFS3eBYkdePPRVquSddX7VukZRFCCOGPUQcbfTq446ftxzC2TxBqVnFUP7714Dn8+/AZ/tu/Q6GWs3b3Sdx79AIp6W/xf0u3I8TXBfcevcCT2DjIZDI4Otiib0cPAEA15wr4sEltTP/5T5ibydC7vTvdiUIIIYQYkVEHG22b1UV2jhLLfjuK8X3bo3qlCtj41xncfxKL//bvgIr2NoVaztBQuc5jXi4N9dZ38m6FTt6tir3dJaWwk1xRvYbDhbvriBBCCL+MPl+4R8v6yMrOwY9bj6BOdWe8jEvCF/07wMGWrqMghJSsq7dCxN4Ek0Yzv5LCMupg4070cwBApYr2qFejEu5EPcOnHT3w/HUinr9OBAA0rl3VmJtEJEQzg+NYUF2xN4e7zBEp1E8LEiwlhJg4ow42Nv11Ruvf1uUs8eeJq+p/y2TAzP+U/G2xxDQM2DFJI4Oj+HcuGWZ7cucEGb57mt4MEao/hGkQf5BICDE+ow42Zhlgfg1SemhmcNzFILE3h7vMESnU8zBIJIQYH92WQQghhBCDMtqRjTm/7EOQRwu0algDZczNdZ7PzslBxN3HOHbhFv7vs87G2iwiIZoZHCM4OPfPW+aIFOpH0GkUQkolow02Bob4YN+pf7D14DnUrOKIyo72KFfWAm8zs/AyLgmPX7xBo1pVMbCIU4yT0kMzg4OHLy3eMkekUA/E6K0nhJguow02qjmXx+c9/JCYkobbUc8R8zIeKWkZsLYqC7cW9TCoiw/sbayMtTlEgjQzOHi4ZoO3zBEp1PPQb4QQ4zP6PBsOttZwb1HP2KslhBBCiEiMPtgozSgbpRj1lI1CCCGSR3ejEEIIIcSg6MiGEUkii4S3espGIYQQyaMjG4QQQggxKFGObDDGEB4Ricu3HiIlPQPThnVF5OMXSExJx0dN64ixSYRTNeb5FpjBYWhSyByRUj0hpPQRZW+w7+Q/OBtxD96tGyIuMQUAUN7OBkfO3RRjcwjHPm7eASejLmL47mmibcOAHZNwJDIcHzfvUKjtoXpx+4sQwh9Rjmycu34fU4d2ga11Ofx26DwAwKm8LV4n0Pl5ok2Vq5FfBoehSSFzREr1hJDSR5QjG0rGYFlWe5yTkZkNSwu6XpUQQggxNaJ8uzev9wF2HL2ET9q5Asi9hmPvyato0aCGGJtDODZh/+wCMzgMTQqZI1KqJ4SUPqIMNj5u1xYb9oZjwg9bkKNkGLdgC5rUqYZBXSgXhWj74+ahAjM4DE0KmSNSqieElD6iDDasLMti5Cf+SEpNR1xiKirYW8PB1lqMTSGcezL5lNibIInMESnVE0JKH1Gu2di4Lxz/PoyBvY0ValdzUg80th48J8bmEEIIIcSARDmyceHmQ/wb9Qz+rk0R5N5c/fjFmw/xaUcPMTbJKCgbpRj1lI1CCCGSJ8qRDYsy5pg8KBiXb0Vh/Z5TyM7JAQAwMDE2hxBCCCEGJNq9phXsbTBxQEds2n8GC349iBEf+0EGmVibYxSSyCLhrZ6yUch7aHRkg9ibYDLuBg0SexOIhIlyZEN1BKOsRRkM6SZH68Y1MXf9fvURDkIIIYSYDlGObAR7u2j9u4NnS3xQqSKu3I4WY3OIRGhmcBwLqiv25nCXOSKF+mlBgqVGIYX2kVI9IUUhyqcoyKO5zmPN63+AgSE0zwbRTzODgwe8ZY5IoV5MUmgfKdUTUhRGO7Kx9LcjGNsn92fNDxsPAHquz5g4oKOxNolIjGYGx10MEntzuMsckUI9cE9vvaFJoX2kVE9IURhtsOHeor76/71cGhprtYQQQggRmdEGG67N351j92hZP59KQoRpZnCMEPHcvwpvmSNSqB8B8a61kUL7SKmekKKQ5aQ9MdrkFo+ev0YZc3NUr1QBAJCc+hbbj17Es1fxqFvdGT0C26JcWQtjbU6+EpOSSnyZTf7NLFSdavKvwt46asr1lzVufVUyJQbumIywqAs4dlz3S6tNs32FWl9RpS5+I/i45vYA7zJB8rsAr7TXf7kqRrDWGH0nhfbhuV7o1ldD9RuRJnMzM9ja2go+Z9TBxg8bDyDYxwVN6lQDAKzYfhyJKWnwaFkfl25FoXqlCtzMIEqDDT7qL+uZZ8OYOz59gw1SdPrmaqC+4x8NNkhB8htsGPVulOevE1G/RmUAQNrbDNx6EIPBXX2h+KgJhoTKcSPyiTE3hxBCCCFGYNR5NpRKJcqY545vomJewd7WCpUdHQAAFe1tkJZRuF/+UkXZKMWop2wUQgiRPKMe2ajqXEE9cdelf6PQpHZV9XPxSamwsixrzM0hhBBCiBEY9chGd/8PsWL7cWw9eA5mZjJMHNBJ/dyV29Go90ElY26O0Ukii4S3espGIYQQyTPqYKN+jcqYPfoTxMYlonJFB5SzfHfnSfP6H+CjpnWMuTmEEEIIMQKjZ6OUs7RArapOOo9X+d+1G4WxcV84btx/CjubcvhmeDcAQGp6BtbsDsObhBQ4lrfFsFAFbKwswRjD9iMXcfPBU5S1KIOBnb1Rs6pjCb0bYmg15vmKntHAWyaF1OuNqTCfH97ah9f6SRzMbUOkS/y9QTF4tKqPMb3baT126OwNNK5dFd+P6oHGtavi8LkbAICbD2LwMi4JM0Z2R99OHth66JwYm0yKiYeMBt4yKaReb0w8vF9TqSfkfYiS+vq+GtSsgtcJ2ufyr997jP/2yw3o8mhRH4s2H0J3/49w/d5juLesB5lMhrrVKyH9bSYSk9PgYGctxqaTIlLlNIiZ0cBbJoXU642pMJ8f3tqH13oxc22I9ElysCEkKTVdPYCwt7VCUmo6ACAhOQ0V7G3UdeXtbZBAgw1CCOHW1VshYm+CyTP2hGwmM9jQJJPJIJMJp8rm5/TVuzj9T+7ofVBnD1SrrHttyft5XcLLM30T9s8udEaDg729QbaBt0wKqdcLMVTfDd//hejv11TqhXJtDNVvxPAM0XcpKSl6nzOZwYa9jZX69EhichrsrMsBAMrbWSM+KVVdl5CUivJ6jmr4tGkEnzaNAOROV26IKctJ0fxx85A6o6EghuqvNaEzMXDHZPUOuqDtofrC9ZcmQ/VdYT4/vLUPr/XAK53naB8pXYboO3Mz/ZeBGjUbpSS9TkjGiu3H1Xej7Dx+CTZWlujg2RKHzl5HanomegR8hBuRTxB2+Q5G9w5E1LNX2Hb4Ir4a3LnA5RuiI65NbotPdqRjxydWUNQueJwXFp1d6uufvv1LsNaYOQ0FZdpIKWvGmPVCuTaUjSJdxvybo9MohmeIvssvG0WSRzbW7j6Je49eICX9Lf5v6XaE+LqgvUcLrNl9EmciIuHoYIth3RUAcufvuPkgBtNW7EJZC3MM7Owt2nbz/sUupXpCCCHSIcm9+tBQueDjE/q213lMJpOhTwd3Q29SofDyRS31emN7cVL486YSJsvd/t9lmwr3fktJ/VMIH5UihJQ+kpxnQ6p4+KKWej1veGsf3uoJIQSgwQZXePui4K2eN7y1D2/1hBCiQoMNTvD2RcFbPW94ax8e6wkhREV6e3kTxOMXBU/1KpqZDseCdO/5Nxbe2ofX+iujdTM4pomYr8Fr5ohU6wkpCvoUiYzXLwpe6jVpZjqIhbf24bleKINDTLxmjki1npCioCMbIuL5i4KX+vpV3v1bM9PhLgYV+PqSxmP78Fx/8lfdDA4x8zV4zRyRaj0hRUFHNkTC+xcFL/W84LV9pFpPCCldaK8gAt52/DzXa9LMdBhhxHP/PLcPz/VCGRxC+RrGwmvmiFTrCSkKyU5XbmiGmK68wkoPyXxR8FKvOV25kikxcMdkhEVdwLHjul9ahpo6uWHd5ty2D8/1j9P3qvsLyM3g+HJVjOByjDFduebnR7U9q0O/z/eCSap/V0/TlZsWY09XToMNPSgbhY96HrJRrk1uy2378Fwv1HeUjSJdNNgwLcYebNA1G0YkpS8K3uuNiYf3K/V6QkjpRnsJI+Jlxy/1et7w1j681RNSVI2ObBB7E0yGvqOJxkZHNoyIhx2/1Ot5w1v78FZPCCEADTa4wtsXBW/1vOGtfXirJ4QQFRpscIK3Lwre6nnDW/vwWE8IISrS28ubIB6/KHiprzHPl7uMBp7ah+f6f8YUWGpwhfn88JY5wmv9JCPObaNkStHfr6nUi5kjpYmPvXcpxusXBS/1vGU08NY+PNfzgIcMEVOpNyYe3q+p1POCjmyIiOcvCl7qVTkNPGQ08Ng+PNc/fVvgSwyuMJ8f3jJHeK03Zq7N4cjTBW4Pb+3Da70YOVJC6MiGSHj/ouClnhe8to9U6wkhpQvtFUTA246f5/oJ+2eLntHAc/tIsd6YCvP54S1zhNd6Y+ba8PB+TaXemDlS+aHpyvWgbBQ+6mXTkwQzHYw5dTJloxSvnofpyitWacZ95ohU6r9e+0rnOUP1W/KiV6K/X1OpF8qRAigbhRuUjcJHPWWjSLeeh8EGZaOUHGP+zVG/lRxj/s1RNgonpPRFwXu9MfHwfqVeTwgp3WgvYUTf9T+GWwBuvdBfc+9ZBH45OgOD232P7/q7lPr6ruX1v5YXvH2x81ZPCCF0ZMOI7j2LKLCmYTUXDG73DX45OoPqJYC3L3be6gkhBKBrNvQyxDUbNWt7YnC7b9CwmkuBte9+8Zfu+q7lLQTrjXn+eOu3+Rya+R+ptKcx64X6jq7ZkC66ZkOa6JqNUoinIwRSrzcmHt6v1OsJIaUbDTaMiLcvAKnVK5kSQ3ZNQY15vgUuqyRJpX14q9fsrxrzfNFv+8QCl2lIQtujZEqqL2a9ofH2fqVeLzYabBiZ2F8AUq7XnPPfmKTSPrzVC2U6iIn3DAup1Rsab+9X6vVioyu8RKC5gy7MOfLSXP9l03fnFTXn/DfmfP88tw/P9UKZDsbM18iL9wwLqdUbGm/vV+r1YqMjGyKR0i9UMet5wWv7SLWeEFK60JENEUnlF6qY9Zo05/wXY75/HtuH53qhTAdj5mvkxXuGhdTqDY239yv1erHRra96GOLWV323UPJwmyKv9Zq3T2rO+S8037+xbn3lqX14rr81do9OpsOXq2IEX2OMWyh5z7Dgvd7Yt76K/X5NpZ6XW19psKGHMQcbAL9fGGLX8zrPBi/tw3P9l03b6jxO82xIF82zIU28DDbomg1O8HZOnbd63vDWPjzWE0KICg02OMLjFwZP9bzhrX14qyeEEBUabBgRD18AUq/nDW/tw1s9IYQANNgwKl6+AKRezxve2oe3ekIIMbkLRKcs34FyZS1gJpPBzMwMU4aEIDU9A2t2h+FNQgocy9tiWKgCNlaW+S7HEBeIfjfsEHcX8fFez8MFot8NO8Rt+/Bcz0MQW5N/M3UeS9mwCgBgO2hEoZZR2uovH04WfJwuEJUmukDUgP7brwO+HtYVU4aEAAAOnb2BxrWr4vtRPdC4dlUcPndDlO3i7RenFOp5mPOf5/aRUj2RJlUGh7HXmd9zPGWO8F7PC5M8sjFlcAhsrcupH/t25S78t18HONhZIzE5DYs2H8L0kd3zXY4hb30V+xenlOqfRR3FHzcPoX0DH6ztPktdY+wjG7y2D8/1PBzZyO92cxWptKex6vP2W7/tE3Ey6qJR57bp4zNC6+9daHtUGUlC+weqf1c/4kfhiABjH9kwuRlEZZDhx61HIJPJ4NO6IXzaNEJSajoc7KwBAPa2VkhKTRd87emrd3H6n9yOGdTZA9UqO5Xw1uXu+Hif+ZGn+tvjcv8gCjPnv4O9fYE1xcFz+0ixXoih+u7es4JPgfHWPrzVv/vi0v3SMlS/HY48XeD28JI5wnu9vhwpQ/RdSkqK3udM7jTKxAEdMXVoF4zuHYiwK3cQ+Vj7l41MJoNMJhN8rU+bRpgyJARThoTAxqosEpOSSvQ/Tbwd4ua1vihKur80+43X9pFqfV6G6jte3q/U6/Ux5N8cMSxj95vJDTYq2NsAAOxtrODSqCainr2GvY0VEpPTAACJyWmw0zjFIibedig81k/YP5ubOf95bB8p1xsDT+9XqvV5MziMoTCZIBP2zy7U/qG01/PCpK7ZyMjMAmNAOUsLZGRm4cetRxDs0wp3op/DxsoSHTxb4tDZ60hNz0SPgI/yXRZlo/BRP/rnAME5/8Wcrpyn9uG5XnUKTJMY12zw2j681ue9ZkOVwfH12lc6rzVUvyUvesV95ohU6oWutQEoG+W9vIpPxqo//gYAKJUMbZvVQSfvVkhJe4s1u08iLjEFjg62GNZdnFtfKRul6PU83PpK2SjSzUahi7KLXi/UbwDd+ipVdOurAThXsMO0YV0xbVhXfPt5N3TybgUAsLUuhwl92+P7UT0wvm/7AgcaYuDlkCmv9bzhrX14rOcJj+3Daz0hhmBSgw2p43kHxEM9b3hrH97qecNb+/BaT4gh0GDDiHjaoUi1nje8tQ9v9bzhrX14rCfEEGiwYUQ87VCkXM8b3tqHt3re8NY+vNUTYgg02DAinnYoUq83Jh7er9TrecNb+/BWT0hJo8GGEfG2Q5Faveac/8YklfbhrV4o04EnYrcPr/WFzeAwNN4zR6RWLzYabBgZLzsUKdYP2DEJRyLD1XP+G4tU2oe3es3++rh5B5yMuljg8oxNSu1prHrN/hq+e1qByzQUoc9PfttD9eL2V0FosCECHnYoUqnXJJQRYAw8tw/P9Zr9tTh4itEHiQCdAitOvWZ/5ZdRYmhCn5/CZqZQPX9osCESsXcoUqnnBa/tI9V6Y+Hl/Uq9npD3RYMNEfG2Q+GxXpPmnP9i4LF9eK4XynQwNp7bh9f6wmZwGBrvmSNSqxebSU1XXpIoG4WPes3pyjXn/Bea75+yUfiqvzV2j06mw5erYgRfQ9ko/NSP/jkAgG4Gh7GnK+c9c0Qq9bxMV06DDT0oG4WPespGkW49ZaNIs56yUUwLL4MNOo3CCakcYhWrnje8tQ+P9TzhsX14rSfEEGiwwRGed0A81POGt/bhrZ43vLUPr/WEGAINNoyIpx2KVOt5w1v78FbPG97ah8d6QgyBBhtGxNMORcr1vOGtfXir5w1v7cNbPSGGQIMNI+JphyL1emPi4f1KvZ43vLUPb/WElDS6G0UPQ92NwvNV6DzWj/45API6rtj4yXytW7qMeWV8zdqe3LYPz/VCdxKJdTeKPlJqT2PV5+03JVNi2O6vMWnVM51aY/cbD+3Dc33X8hbq/joSGQ4AgtMEAHQ3isnj7RcM7/U8zPnPc/tIrZ43vLUPb/XAuwwOHvDWPrzVA7qZKbygwYYIePuA8lzPw5z/PLePFOuNiYf3K/V6VQYHL3hrH97q82am8IIGGyLh7QPKaz0veG0fqdYbCy/vV+r1vOGtfXir5xENNkTE2weUx3qe5vznsX2kXG8MPL1fqdarMjiMSUrtw1t93swUXtBgQ2S8fEB5rf/j5iG0b+CDXz+ZV2CtMfDWPjzX84Dn9pFK/ZrQmUYf7EupfXirV/XXHzcPiRJ+qA8NNjjAwweU1/onk09hbfdZesOIxMBT+/Bczwte20cq9WYyM2zquaDA5ZQkKbUPb/Wq/noy+RSeTD5V4PKMhZ89eCkn9geU93re8NY+PNbzhMf24bWeBzy3jxTreUCDDY7w9gHlrZ43vLUPb/W84a19eK3nBa/tI9V6sdFgw4h4+MBJvZ43vLUPb/W84a19eKznCY/tI+V6MdFgw4h4+cBJvZ43vLUPb/W84a19eKvnDW/tw3M9z2iwYUS8fkClWG9MPLxfqdfzhrf24a2eN7y1D6/1PKPBhhHx+gGVSr2SKTFk1xTUmOdb4LJKklTah7d6zf6qMc8X/bZPLHCZxiR2+/Bar9lfSqYscLnGwkv78FzPU3/lRYMNI+PxAyqVes05/41JKu3DW33ejIaTURcLXJ6xSak9jVWv2V9iZhIJ4aF9eK7nrb800WBDBLx9QHmu16Q5578x8dw+PNfnzWgQI1+D5/bhtV6zv8TMJNJH7PbhuZ7H/lKhwYZIePqA8lzPC17bR6r1xsLL+5V6PW94ax/e6nlEgw0R8fYB5bFek+ac/2LgsX14rs+b0SDG1Mk8tw+v9Zr9JVYmEc/tw3M9DxlS+tBgQ2Q8fEB5rtekOee/WHhrH57r82Y0tG/gU+DrSxrP7cNrvWZ/iZVJxHP78FzPS4aUEBpscEDsDyjv9Sqac/6Libf24bU+b0bD2u6zCnytIfDaPrzWa/aXWJlEPLcPz/U8ZUjlxe+WlTJS+kCLUc8b3tqHx3qe8Ng+vNbzgOf2kWI9D2iwwRHePqC81fOGt/bhrZ43vLUPr/W84LV9pFovNhpsGBEPHzip1/OGt/bhrZ43vLUPj/U84bF9pFwvplIz2Lj14Cm+XbkL01bsxKGz10XZBl4+cFKv5w1v7cNbPW94ax/e6nnDW/vwXM+zUjHYUCqV+O3QBYzu3Q7fft4Nl25F4dmrBKNvB68fUCnWGxMP71fq9bzhrX14q+cNb+3Daz3PSsVgI/rZa1SqaAfnCnYoY26Otk3r4Pq9x0bfDl4/oDzX85DRwHP7SKmeN7y1D2/1KqqMG7Hx1j481gO6mUS8kOWkPWFib4ShXbkdjX8fxKB/Zy8AwPkbDxAV8wp9Orhr1Z2+ehen/7kHAOjX/kM8jIo29qYSAzAzM4NSyW9AESka6k/TQX1pWmrXqoHaNWsIPlfGyNvCNZ82jeDTphEAwNy6Bhgz+XFYqSCTyagvTQj1p+mgvjQtMpkMOWlPBJ8rFadRKthZIz45Vf3vhKRUVLCzFnGLCCGEkNKjVAw2alVzwsu4JLxOSEZ2Tg4u/RuFlg2FD/VI2YYNG7Bhwwa8ePECs2blztb4yy+/6NSFhYXh66+/fq91CS1j9erVcHd3h7u7O7Zu3fpey5cy3vshJiYGkydPLvK67t69C4VCAXd3d1SuXBkKhQJfffUVBg0ahPv376vrFAoFAGDQoEFwc3NT1718+RL//e9/i7xeMYndlwcPHkTjxo3h7e0t+JpLly5hyZIlRV7XyZMnoVAo4OLiglq1akGhUODHH3+EQqFAdna2uk7VlwqFAnK5XF137do1zJ8/v8jrNRVify6Sk5MREhICLy8vbNy48b2WbyylYrBhbmaGXu3dsfS3o/hu1Z/4sEltVHOuIPZmGUyVKlUwdepUAMJ/AIYSFBSE8+fP4/Tp01i4cKHR1ssrXvth5cqV6NevX5GX26hRI4SFheH3339Hu3btEBYWhjlz5uT7mi1btqjrKlWqhFevXiEpKanI6xabWH3p7u6Oa9eu6X3+p59+woABA4q8XLlcjrCwMCxZsgT9+/dHWFgYxo0bl+9rjh8/rq5r1aoVzp07V+pPgYj1uVizZg169+6NU6dOYe3atcjMzDTauourVAw2AKBF/Q8wY2R3zPxPD3TybiX25hTo2bNn8PPzg7e3N0aNGgUgdzS9du1aAMB3332HsLAwZGZmomvXrujQoQP27t0LAIiOjka/fv2wd+9e3LhxAwqFAkePHhVcz9q1a+Hj4wMfHx9cvXoVW7duxcqVKwEAN27cwKhRo8AYw8iRI+Hv74/g4GDEx8cLLqt27doAgDJlyqBMGdO4HMgU++H8+fNo0aIFgNwvs2HDhsHFxQWHDuUG3G3evBnu7u7w8vLK94uuODw8PHDs2LESXWZhSbEvK1SoAEtLS8HnGGOIiYlBxYoVAQAtWrTAp59+ilatWiEiIgIAMG/ePHh5ecHf3x+PH5fsHXgNGjTAP//8U6LLFIMUPxfnz59Hu3btYG5ujlatWuHOnTsl3SwlrtQMNqTGyckJR48eRXh4OJKSkhAZGSlY9+eff8LV1RWHDh2Ck5OT1nNdunRBixYtEBYWhnbt2um89vXr19i7dy9OnTqFPXv2YMaMGQgJCcFff/0FANi5cyc+/vhj/PXXX6hZsyb+/vtvjB49GqtWrcp321etWoWuXbsW853zxRT7QfNXUFxcHGbNmoX9+/fj559/Rk5ODpYuXYrTp09jy5Yt6l9txdW3b18oFAps27YNAFC3bl3RdoxS7kshr169Qvny5dX/fvnyJX755ResWLECv/76K168eIG///4bZ86cwYwZMwo8ClWQgIAAKBQKnDx5EoC4fVmSpPi5SEhIgL29PQDAwcEBCQkJ79ECxmEaPz9N0Js3bzBy5EgkJCQgOjoaz549g0wmUz+vOnz58OFDtG7dGgDw4YcfFmkdDx8+xLVr1+Dn56d+zM7ODpaWlnj9+jVOnTqFadOmYeHChfj9999x+PBhZGdnw8PDQ+8yL1y4gAMHDuDPP/8s0rbwytT7wdnZGZUqVQKQuwN79eoVatWqBQsLC9SuXRuJiYkFbn+5cuWQkZEh+NyWLVtQv379ApdhDFLty8KqX78+ypUrh+rVq6vfY8uWLQEAH330EaZPn17gMlR9WaZMGbx9+xZWVlbq544fP24yRyw1SfFz4eDggKSkJJQrVw5JSUlag05e0ZENTm3duhXdunVDWFgYvLy8wBiDg4MDnj9/DiD3sBsA1KlTR32oW+iQpuYfTV516tRB27ZtERYWhrCwMPXhv27dumH+/Plo0KABzM3N0ahRIwwYMABhYWEIDw/H7NmzBZcXExODL774Ar/++ivMzc3f6/3zwhT7wcLCQnC7GGNwdnbGo0ePkJWVhejoaDg4OBTURGjWrBnOnj0LAIiMjETlypX11j58+BCNGzcucJmGIMW+zI+zs7PWL9q8fVm7dm31+7h8+TLq1atX4DI1+zI8PBzNmzfXWytmX5YkKX4uPDw8cPz4ceTk5CAiIkIS/UCDDU75+/tj4cKF6NatG1JTc2/bDQgIwKFDh9ClSxd1Xbdu3XD27Fm0b99e8FCaq6srunXrhtOnT+s85+zsjODgYPj6+sLPzw9z584FkHtIcPny5ejRo4f639HR0fD394e/vz8OHjwouM0zZsxAbGwsunfvDoVCgfT09PdtBtGZYj+4ubmpd6B5mZub4z//+Q98fHzw6aef4vvvvy+wjQYPHowjR47Az88Pn3/+eb6vOXv2LAICAgpcpiFIsS8vX76MwMBA3Lx5E4GBgXj79q36OZlMhmrVqiEuLk7wtVWqVIGfnx88PT3x9ddf46uvviqwjb788kssXrwYCoUCixcvxpdffqm39t69e3BxcSlwmbyT4udi6NCh2LJlC3x8fDB48GCULVv2fZvB4ErFDKLFQZN6mQ6aOEjb06dPsWzZMsybN8+o63358iXmzJmDxYsXv9dyqD/fuXTpEs6cOYPx48cbdb3Xrl3DoUOHinULtSbqS9OS36ReNNjQgwYbpoN2aKaF+tN0UF+allI/gyghhBBCxEODDUIIIYQYVL73MdVt7IlHj4UPiZi6cuXK5Xt1MZEO6kvTQv1pOqgvTYu1tf7MsXwHG48ePym159PoXKLpoL40LdSfpoP60rTkN3Ck0yiEEEIIMahiDTbCwsLUSYEKhUI9T3xe0dHR+PvvvwFAKx2vsKKjo9Xpkm5ubur7l+fOnYuYmJjibDrR48KFC/D09IS3tzcmTJigfvzo0aPw9/eHQqHAlStXAAALFiyAt7c3+vbti6ysLK3lvH37FkOGDIG/vz/GjBkDABg/frz6s1Khgm4AnirRMjIyEu7u7nj58qX6tarkUF9fX/Tv359+BRXSs2fP0KZNG5QrV06d4pmWlobg4GAoFAp07doVGRkZyM7ORu/eveHn54dJkybpLEeo75RKJSZOnIjAwEB88sknOq+h/ix5Qv2psnjxYp1UWKHHAODmzZvw9vaGl5cXrl+/DiD3b9zd3R1+fn4604+rsj+A3DlS/Pz8kJaWpu5PVRqsl5fXe98GW1ro68sJEybAx8dHHYiXX58DwKxZs1CtWjWtRNhx48ZBLpfDzc0NZ86c0arXTI/duXMnevTogWfPnqm/lxs1aqT+rl26dGmJv2/kpD1h+v4DwIScOHGCTZ06VfC5otTl5OTk+/qoqCjWt29fxhhjMTExrEuXLgWusyAFrVNF33s3Vc+fP2fp6emMMcY+/fRTdv36dZaWlsZ69OjBsrOz1XWxsbGsY8eOjDHG5s6dy7Zv3661nHnz5rFjx44JruPq1avq/tTk5eXFYmNjmZubG4uMjNR6buDAgerHhg0bxq5evVrk91ba+pIxxtLT01lcXByTy+UsKyuLMcbYzp072fTp0xljjM2cOZP9+eefbPv27Wz27NmMMcZGjx7NIiIiBJen2Xfbtm1ja9eu1btu6s+SJ9SfjDH29u1bNmDAAObl5ZXvYyrdunVjjx8/Zk+fPlXvT729vVlKSgp79uwZ69mzp1a9ah989+5d5u7uzl69eqX1vOb2tGvXjsXFxRXpfVFf5rbdlStX2NChQxljjI0YMYJdvHhRb5+rvHjxgv39999a37GZmZmMMcaio6NZp06dtOpV38fh4eHMz8+PpaWlaT2v+rwolUrWtm3bYr03AHrHEyV2GiUsLAwdO3ZESEgIvLy8kJKSgtWrV2PTpk0ICAjQGiG7u7tj5MiRmDhxIu7fv4+goCDI5XLMnDlT7/KTkpLUwTODBg3C/fv3sWHDBvTo0QOdOnVCp06dwBhDRESEemSnmup1w4YN6NWrF4KDg7FgwQIsX74cABAREaEeoZd2VapUQbly5QDkTmdtbm6Oc+fOwczMDB07dkT//v2RmpqKy5cvQ6FQAAACAwNx7tw5reWEhYVh7969gke8du/eje7du+usOy0tDT179sSPP/6oztEQ+lWWnJwMOzu7kni7Jq9cuXI6R5Hq1aunniExISEBjo6OePjwoTo/w8XFRT1VdV6afffXX3/h1q1bUCgUWLNmjU4t9WfJE+pPAFi3bh0GDhxY4GMq8fHxqFGjhjo/RcXGxgZVq1bFgwcPdF4TGxuLAQMGYPPmzeoAsrz9qVQqkZ2dLYmZLMUm1JeqFFfg3X5VX5+rVK5cWecaCVUUQUpKClq10k03v3v3LiZOnIgdO3bAyspK63tZJTMz0yD9WOzBxqZNm9SHVy9evAgAKFu2LPbt24dOnTrh+PHjGD58OPr374/jx49rvfb169eYOnUqFi1ahKlTp2LdunU4efIkbt26hadPn2rVHj16FAqFAn5+fhgwYIDOdnzwwQc4cOAAqlevjuvXr6NRo0YICwvDhQsXcPToUfVUzeXLl8f+/fsxduxYHDhwAACwbds29OnTp7hNYJKuX7+OV69eoWnTpoiNjcXz589x8OBBeHp64ueffy4wbfDBgwcIDg7G/v378f3332sd/jt06BA6dOigs8779+/DzMwMrq6ugtvUt29ftGnTBi9fvkTdunVL7s2WMg0aNMC5c+fQrFkzXL58GZ6enmjUqJE6xfPEiRN60yM1+y42NhaNGzfGsWPHsGXLFsTGxmrVUn8aR1ZWFsLCwuDv75/vY5qUSqX6/5nGKazY2FjcuXMHt2/f1nnNxYsX0bhxY73ZKgEBAWjRogVq1KgBGxub4r6dUq0kU1xDQ0MRFBSEwMBAneeOHDmC9u3bw9HRUee5V69eQaFQoGnTpgaJFCj2YKN///7qUBnVTkUV2pN31JxXpUqV8MEHHwDIHWn1798fCoUCt2/f1rkWo127dggLC8P9+/cFr/nIu86oqCh06tQJcrkct2/fxsuXLwG8S+mzsrJCpUqV8PjxY1y4cKFE0hZNRVxcHEaPHo1169YByP3Qe3t7w9zcHP7+/rh9+7Y6bRCAYNqgg4MD5HI5bGxsUL9+ffUXUWRkJKpXry54a1TLli3h5eWlde5R05YtW3D16lV069YNW7ZsKcF3XLr8+uuvCAkJwa1btxAcHIzNmzcjJCQE6enpCAgIgKWlpWCIWt6+U/VxmTJl4OHhgfv372vVU38ax6ZNm/Dpp58W+JgmzV/CZma5u//58+ejd+/emDt3Lry8vHReExISAqVSibVr1wou8/jx47h16xacnJwEc0FIwQrarxbF7t27ceHCBUyZMkXnuZEjR+LMmTM4dOiQznPOzs7q79rbt2/j0aNHxd4GISV6N0re1EELCwvk5OTortTs3WobNWqE3377DWFhYbhy5Qratm0ruGwrKyukpaUVuM6VK1di8uTJOHnyJOrXr68evWuu89NPP8UXX3wBV1dXusf7f7Kzs9GvXz/88MMPqFKlCgCgbdu26l86ERER6uRC1S/hY8eOwd3dXWs5np6euH79OnJychAdHQ1nZ2cAuX8AoaGhetc/Y8YM3LlzB7/99pvemvLly+sNnSIFY4yhYsWKAAAnJyckJibC3Nwcy5Ytw/Hjx2Fubo727dvrvC5v36n6GMhNxKxVq5bOa6g/De/u3btYuXIlOnTogFu3bmHZsmWCj2mqWLEinj59imfPnql/SXt4eODEiROYOnUqmjRpIriuNWvWYNOmTTh16pTe7aH+LD5ViisgvF8trIyMDACAra2t4FGmMmXKYNu2bfj66691LgZWkclksLOze6+jK0LynWcjP5s2bUJ4eDgAYMiQIahRo4ZOTfPmzfHVV1+hV69eekOfZs2ahcGDByMjIwMWFhbYuXMnbG1t1c+rTqO8ffsWw4cPL3C7goODMXr0aDRt2lTveaeAgAAMGDBA7y+v0mjHjh24dOmS+o6EOXPmwMPDA3K5HL6+vrC2tsbWrVtRsWJF+Pr6wtvbGzVr1lQHQI0ZMwbLli3D5MmTMXDgQCQlJWHYsGHqPvjrr7+wZ88eveuXyWTYuHEj2rVrp3O4tm/fvrC2toaFhUW+X17knaysLHTs2BHXrl1D+/btMXv2bHz66afo1asXNm3aBAsLC2zbtg0xMTHo27cvzMzMMGDAAFSvXh3Au/4EdPtuyJAhGDhwIJYsWYL27durj1Jqov4sWUL9qblP9fb21rn+TPXYixcvsG7dOkydOhXTp09Hr169AAA//fQTgNx98LFjx+Do6Iiff/5ZcP2WlpbYtm0bOnXqhJ07d2o9FxAQAJlMBkdHR7ojpRCE+tLNzQ3lypWDj48PXFxc4OrqKlhXq1YtdV+uW7cOK1asQFxcHOLj4/HTTz+hV69eSEhIQE5ODubMmSO4/ooVK2LTpk3o168fVq9erX5cdRpFqVSiSZMmgtd8vI98g9hMNYwsJycHHTp0wNGjR/XW0GQzpoP60rRQf5oO6kvTQkFsGuLi4hAYGIghQ4aIvSmEEEJIqVAqj2wUBo24TQf1pWmh/jQd1JemhY5sEEIIIUQ0+V4gam1tXWrv1qA0QtNBfWlaqD9NB/WlaSl26mtaWlqpPcRFh/dMB/WlaaH+NB3Ul6aFUl8JIYQQIhoabBBCCCHEoN57sLFhwwat+fYL45dffnnf1Qqun6Lni08oYv7169fw9PSEXC5Hly5dkJ6ejqioKPj4+MDX1xeffvqpzgyxGzZsUEcVqyYI27dvH9zd3eHh4YGFCxfqrFuhUCA7Oxvp6enw9/fHmTNn1H2puTw/Pz+dHA4iTF889caNGxEQEACFQoGYmBhER0ejcuXKUCgUCAoKElyOv78/PD09cezYMQDA9OnT4eHhoTXroSaKmC95Qv0p9FhaWhqCg4OhUCjQtWtX9YySKkJx8m/fvsWQIUPg7++vMzEYRcwbh1C/Ce2TNY0fP16dT6YKbBPa/2pS/W2+evUKHh4euHfvHsaPH4+cnBx89913aNWqFRQKBYKDg9W5YiWmOBHzmvTF3+ZHKPq4uIqz/sIozHs3JUIR89nZ2SwnJ4cxxth3333Htm/fzuLi4lhCQgJjjLEpU6awvXv3ai1n/fr1bM2aNVqPPXr0iGVnZzOlUsl8fX3Vr1eRy+UsIyODhYaGsp07d+pd3pYtW9iiRYuK/N5KW18yJhxj/fTpUzZ48GCtOlWEuD5jxoxh4eHhLDk5mcnlcsYYYw8fPmSMMRYfH898fHx0XkMR8yVPqD+FHtu5cyebPn06Y4yxmTNnsj///FNrOUJx8vPmzWPHjh0TXC9FzBuHUL8J7ZOFXL16Vf03LLT/1eTl5cVSU1OZQqFgZ8+e1Xru22+/ZUePHmWMMTZr1iy2a9euIr8PGCpi/uLFi4iIiEBAQAA2bdqkExefk5ODoKAgxMTE4PDhwxg/fjxWr16NGzduQKFQ4MaNG+pl7d69G66urvD398eBAwfAGMPIkSPh7++P4OBgxMfHQ6lUYujQoZDL5ejYsaPO+lXR84mJiejcuTN8fX0xduxYABCMoyfvCEXMm5ubqzNlcnJy0KBBA1SoUAEODg5adXktWbIEvr6+6l+9NWvWhLm5OWQyGcqUKaOVU6MyZswY+Pn5qWPMVX2pKSkpSZ3nQPInFE99+PBh5OTkICAgAGPGjFEflTpx4gR8fHywePFineXcuHEDnp6esLW1hZ2dHZKSklCnTh0AuVNYC10QRhHzJU+oP4Ueq1evHlJTUwHkJokKpXvmjZMPCwvD3r17oVAosHfvXp16ipg3PKF+E9onC9m9e7d6vwno7n815eTkoG/fvhg3bpw6hFR1ZFmTQfa1JXlko2fPnuzx48eMMcZ69+7Nnjx5wm7evMlCQkKYXC5nKSkp6tFVXv369WNRUVGMMcaUSiXbu3cvmz17NmOMsQMHDrDZs2ezXbt2sa+++ooxxtS/uDXXr/rVNH/+fLZx40bGGGNDhgxh58+fZ+vXr2djx45ljDE2dOhQFhERUeAIrTS6du0a69Spk/rfFy5cYB9++CHz8vLSOiIRExPDPD09dY4qxcfHs5ycHPby5Uvm4uLCsrOz1c8dOHCADRs2TGedcrmc1alTh71+/Vr9mKov169fzxo2bMi8vLxYzZo12fPnz4v8nkprXzKm/fcxe/Zs1qdPH8YYY5MmTWI7d+5kb9++ZSkpKSwrK4t16tSJXbt2Tev1mkcu+vbtyx49eqT+96RJk9iWLVt01mlnZ8f8/PyYUqlUP6b6mx84cCBzdXVlrVu3Zv7+/uq/46Kg/szS+1hqairz8fFhTZs2ZQqFQqd9vb292YsXL9jt27eZtbU1Y4yxhg0bssOHD7OUlBT20UcfaS0/KiqK2dvbs4EDB2otR9Wfcrmc+fr6sqZNm7IBAwYU+f2U5r7UlF+/5d0n59W2bVuWmprKGMt//8sYY40bN2YtWrRQHzFh7N3n59tvv2UtW7Zkbm5urFmzZiwtLa3I7wOGOrKRl1BcfLNmzZCeno727dsLptCpTJ06FTNnzlT/or19+zZ+//13KBQKzJo1C3Fxcbh37x48PT0BQPDXscqDBw/Qpk0bAMBHH32k/oWcN46eaMsbMQ8Arq6uuHz5MkJDQ9XX2mRkZGDgwIFYs2YNypTRvnu6fPnyMDMzg7OzMxo2bKi+xuLhw4eYP3++4K9nAFi+fDl69+6NrKwsnee+/PJLhIeH46+//sLUqVNL6u2WOqpoeADw9/fH7du3YWlpCRsbG5QpUwadO3fGzZs3tV6j+XemGX29e/duvHnzRjDOnCLmxfPrr78iJCQEt27dQnBwMDZv3qz1vFCcvOpzYWNjg/r16+tcF0UR84anr9+E9smaIiMjUb16dfX8Fvr2vyqOjo4YN24chg4dKri8hQsX4vz585gzZw5++OGHEnyHJXCBqGaMvFBc/MGDB9GkSRMcO3YMr1+/BiB8L26tWrWwdu1aDB8+HIsWLUKjRo0wYMAAhIWFITw8HLNnz0ajRo1w/vx5AFBfFCoUY1+vXj1cuXIFAHD58mV16mTeOHryjlDEfGZmpvp5e3t7WFlZAQCGDx+O//znP2jatKnOcpKSkgAA6enpiIyMhLOzM5KTkzFo0CCsW7dO74AzKCgInTt31rlATRNFWL8fzWj4iIgI1KlTB8nJyernz5w5o5PQ2rJlS5w7dw6pqanqQ6vXr1/HTz/9pE4NFUIR8+JgjKFixYoAACcnJyQmJmo9LxQnr/pc5OTkIDo6Gs7OzjrLpYh5wxLqN6F9cl67d+9GaGio+t9C+9+8hgwZAmdnZ72psICB+rI4p1GeP3/OZs6cyRhjbPHixaxDhw7sjz/+YJGRkaxDhw7Mz8+PBQUFsaSkJKZQKFhqaiq7ePGi+jBbnz59WPfu3dnt27fVy5w0aRLz9fVlbdq0YSdOnGBKpZKNHj2a+fn5MT8/P7Znzx6Wk5PDBg8ezHx8fFjHjh111q869B4fH886duzIvL292ahRoxhj2hfOfPvtt+zEiRMFHg4qTbZu3cqcnJyYXC5ncrmcnT17ll24cIH5+voyhULBunfvzlJTU9nZs2eZra2tuk51EdHo0aMZY7kXkrq7uzNXV1e2bds2xlju4fsPPvhA/RrVBYYqmoeBhw0bxpYuXapzGkWhUDBPT092/vz5Ir+30taXjDGWmZnJAgICWPny5Zm/v7+63b744gsml8tZjx49WEZGBtu/fz9r06YN8/DwYJMmTVK/XtWfT548YX5+fszd3Z0dPnyYMcZYUFAQa968OZPL5axLly4661YdYk9JSWEeHh7swoULOqdRFAoFa9eundaps8Ki/sztT6HH4uPjWVBQEJPL5SwwMJC9efNGa389c+ZMplAoWI8ePdRt/+zZM9auXTvm5ubG1q5dq7VezQuInz9/zlq3bs0ePnyocxpFLpez7t27s4yMjCK9r9LYl0KE+k1on8zYu79NxnJPc2pelCu0/9Wk6rfs7GzWqVMn9ueff+qcRlH16f3794v8PpDPaRQKYtODZrYzHdSXpoX603RQX5oWCmIjhBBCiGhosEEIIYQQg6LUVz0ojdB0UF+aFupP00F9aVoo9bUY6Fyi6aC+NC3Un6aD+tK0UOorIYQQQkRDgw1CCCGEGFSxBhuJiYnqtDkHBwcoFAp89tln6lRIIHe+dQBaSXKfffZZiWw0MQ59iaBHjx6Fv78/FAqFevI0Fc3+XrRokdZzXbt2FZxVklJCxaMvHVYlLi4OPXv2hL+/P2bNmgUA6N27NxQKBTw8PODi4qJVTymh4iooKXTu3LmQy+Vo27Ytdu/erX6cMQYXFxedWUKpP8U1YcIE+Pj4YNy4cTrP3bx5E97e3vDy8lJP1qfaXyoUCmzdulWrXvS+fN9sFNUkIZqJcYwxdUJk3selojDv3dQJJYKmpaWxHj166My5r6Kvv69du8batWvHpk6dqvMcpYSKRyg5VNPYsWO1Jt/TtGvXLp3+NHRKKGPUn/kpKCk0MzOTMcZYcnIyc3NzUz++Z88eFhgYqJMYSqmv4rly5QobOnQoY4yxESNGsIsXL2o9361bN/b48WP29OlT9eR6mvvLvIz1t2mUbBRievImgp47dw5mZmbo2LEj+vfvr04q1DR58mQEBgYiIiJC/djSpUsxatQowXVQSqh4hJJDNd28eROzZ8+Gn58fzp07p/Vc3rRJFUoJFU9BSaEWFhYAcqezVmVFAcDWrVvRu3dvwWVSf4rj/PnzaNeuHQAgMDBQ5+8vPj4eNWrU0Mr6kslkGDBgAEJCQvDo0SOdZYrZl0YZbHzxxRdQKBT48ccfjbE6UkKqVq2Ke/fu4cSJEzh27BiuX7+O2NhYPH/+HAcPHoSnpyd+/vlnrdeMHTsWV65cwcqVK9WH5+7cuQNnZ2d1iFde9+/fh5mZGVxdXQWf79u3L9q0aYOXL1+ibt26JfoeSf7Onj2Lr776Cr///ju+/PJL9eNZWVm4ceOGOvBQ08WLF9G4cWOdnBWVgIAAtGjRAjVq1Mg3nJEU3/Xr1/Hq1SvB/KJRo0ahZcuW8Pf3BwAcOXIEcrlcb4Q59ac4EhIS1DHvDg4OOuGhqnww4F3W18KFC3H27FlMnjwZX3zxhc4yxezLEhtslCtXDhkZGYLPLVy4EGFhYYLnnQi/hBJBHRwc4O3tDXNzc3VyqCZVmFCDBg3Ujy1atCjfvqeUUH41bNgQTZo0QeXKlbUSYMPCwtTXZeVFKaHiKigpdMWKFbhz5476Gpy1a9fmez0d9ac4HBwc1MFqmonLKpq3mar+NlX7X29vb7x48UJnmWL2ZYkNNpo1a4azZ88CyD28o2+UTKRDKBG0bdu26gGGKjlUk+qP4/Xr1+oLDh89eoRBgwZh0qRJ+O2333Dy5EmddVFKKJ8aNmyI58+fIzU1VesC0rxpk3lRSqg4CkoKVf0gtLKyUv9qvnfvHrp164aFCxdiyZIluHPnjs7rqD+Nz8PDA8ePHwcAHDt2DO7u7lrPV6xYEU+fPsWzZ8/Ufana/969e1fvkWSx+jLfSb2KIjg4GHv27IFCoUBOTg4WLFhQUosmIjl9+jSmTZsGS0tL+Pj4wM3NDQAgl8vh6+sLa2tr9RXPY8aMwbJly/Dll1/i5s2bUCqVmDt3LgDg8OHDAHJ/DR87dgxyuVxnXTKZDBs3bkS7du10DvH17dsX1tbWsLCwyHcwQoouKysLHTt2xLVr19C+fXvMnj0btWrVwrp16zB16lRMnz4dffr0QXp6Or799lsAuYdsz507h+XLl+tdrqWlJbZt24ZOnTph586dWs8FBARAJpPB0dGR7mAoYTt27MClS5cwadIkAMCcOXPg4eGh/vscN24c7ty5g8zMTPVpMdW1VRs2bEB2djYaN26ss1zqT+NT3SXm4+MDFxcXuLq64sWLF1p/m7169QIA/PTTTwBy95Xx8fGQyWRYuXKl4HLF6ktKfdWDZrYzHdSXpoX603RQX5oWSn0lhBBCiGhosEEIIYQQg6LBBiGEEEIMiiLm9aDoY9NBfWlaqD9NB/WlaaGI+WKgC5dMB/WlaaH+NB3Ul6aFIuYJIYQQIppiDzbCwsJQq1YtKBQKeHl54ccff4RCoUDt2rXVqZ95J28KCQnBvXv3AABff/01/vjjj/fbemJQQgmSSqUS/fr1g6+vLwIDA/H69Wut1zx79gz+/v7w9PRUpwBPnz4dHh4eWpPUaKLUV+Moqf7ML9kXoP4UW35JoePGjYNcLoebmxvOnDmjfjw9PR1VqlTRSu4Gcvfzqpl9d+7ciR49euDZs2fq2UcbNWoEhUIBNzc3LF261IDvqnQqaurrjh074OrqCjc3N+zZs0erXvS+LG7q64kTJ9SJj2fOnGFjx45ljGmnft6/f58tW7aMDR48mDHG2I0bN1iPHj3Y48ePWUBAQJET5Ywpv/deWgglSF65coV99tlnjDHGNm/ezJYsWaL1mjFjxrDw8HCWnJysTv59+PAhY4yx+Ph45uPjo7MeSn01jpLqz4KSnKk/xVNQUqgq9TU6Opp16tRJ/fjSpUtZYGCgTr+q9vPh4eHMz8+PpaWlaT2vSv1WKpWsbdu2Rd5e6kv9ipP66uHhwVJTU1laWhrz9fXVqjd0XzJmhNTXpKQk9XSpmurVq4eAgADUrFkTANC8eXNUrFgRPXr0UI+mCL+EEiSrV6+OnJwcALlBQY6OjlqvuXHjBjw9PWFraws7OzskJSWppzS3tLQUPKdHqa/GUVL9CQgn+6pQf4qnoKRQVeprSkoKWrVqBQDIzMzE+fPn4eXlJbjMu3fvYuLEidixYwesrKwQHR2Nfv36adVkZmZS4msJK07qa7169ZCamoqUlBTB72Qx+/K9BhubNm2Cr68vPvvsM/Ts2VPn+YiICAwePBgODg7qHZq/vz9evnypN+GT8EczQdLJyQnp6elo0qQJVq5cqRMxnpOTox5Q5E0q/O677/D555/rLJ9SX43rfftTKNlXE/WneApKCgWA0NBQBAUFITAwEEDuNOV5v3A0HTlyBO3bt9cZiALAq1evoFAo0LRpUwQEBJTMmyAAipf6GhoaitatW8PFxUXwb1PMvnyvwUb//v1x6tQpREREYMqUKTrPu7i44Ny5cxg/fjzMzc2RnZ2N5cuX45NPPqH0TonImyB55MgRODs74/bt2/juu+/www8/aNVrJoNqJhXu3r0bb968waeffqqzDkp9NZ6S6E+hZF9N1J/iKSgpFMj9W7xw4QKmTJmC7OxsHD58GB07dtS7zJEjR+LMmTM4dOiQznPOzs4ICwvD/fv3cfv2bTx69KjE3ktpV5zU1xkzZuDff//F7du3MWPGDJ1litmXJXIaRfPwan5WrlyJXr16Yfr06Vi5cqXeSHrCB6EEScaY+svGyckJiYmJWq9p2bIlzp07h9TUVPXptevXr+Onn35ShwUJodRXwyup/hRK9s2L+lMcBSWFqva5tra2sLGxQWxsLB4/fowOHTpg8+bN+OqrrxAfH6/1mjJlymDbtm34+uuvBRNhgdwvPjs7O8EjKaR4ipP6amlpCWtra9jY2CAzM1NnmWL25Xulvm7atAnh4eF4+/at3l8xKgkJCep4cQsLCwwZMgRLly5VJw8S/gglSAYFBeGXX36BQqGAUqnE+vXrAbxLfZ00aRIGDBiA9PR0TJ8+HQDw5ZdfIjY2Fu3bt4eDg4POVdIApb4aQ0n2Z95k37yoP8VRUFJor169kJCQgJycHMyZMwfVq1fHpUuXAOSe5vT29kaFChV0lluxYkVs2rQJ/fr1w+rVq9WPqw69K5VKNGnSRH0dCHl/xUl9HTlypPram+HDhwsuV6y+pNRXPWiyGdNBfWlaqD9NB/WlaaHUV0IIIYSIhgYbhBBCCDEoGmwQQgghxKAo9VUPSiM0HdSXpoX603RQX5oWSn0tBrpwyXRQX5oW6k/TQX1pWij1lRBCCCGiea/UV825NTRTHe/fv//+W0a4cOjQISgUCigUClStWhV//vknAGDBggXw9vZG3759kZWVpfUaoaRQIHcCKRcXF6xdu1arXnN+/rNnz8LPzw9paWnqz5RCoYBcLoeXlxcmT55swHdr+jZu3IiAgAAoFArExMTo7V9Nefvt7du3GDJkCPz9/XWmRKa+NJ7o6GhUrlwZCoUCQUFBAICsrCx4eHjA1tZWcD88d+5cyOVytG3bFrt37wZAKb48EEpkjoiIUP9t1qlTB0uWLNF6zerVq+Hu7g53d3ds3boVALBv3z64u7vDw8MDCxcu1FmPQqFAdnY20tPT4e/vjzNnzmDu3LmIiYnBhg0b1Mmvfn5+iI2NLdk3WRKpr5o0Ux2lLL/3Xlq5urqy5ORkFhsbyzp27MgYY2zu3Lls+/btWnVCSaGMMbZnzx4WGBjI1qxZo1UfFRXF+vbty+7evcvc3d3Zq1evtJ6Xy+UsKyuLMcZYu3btWFxcXJG2m/oy19OnT9UJzEJU/ZtX3n6bN28eO3bsmOAyDN2XjFF/qqjaWpNSqWQvXrzQux9Wpb4mJyczNzc3xpi4Kb7Ul7mEEpk1denSRafto6KiGGO5fdqmTRvGGGOPHj1i2dnZTKlUMl9fX5aQkKD1GrlczjIyMlhoaCjbuXOn1nPr169X/41v2bKFLVq0qMjvA4ZOfQW0Ux3nzJkDX19ffPPNNwByZzdzd3eHn58frl69WlKrJEb08OFDVK5cGba2trh8+TIUCgUA4TRCfUmhW7duRe/evQWXHxsbiwEDBmDz5s1wcnICoJsUqlQqkZ2dTemSxXT48GHk5OQgICAAY8aMUYcjAtr9m1fefgsLC8PevXuhUCiwd+9enXrqS+M5ceIEfHx8sHjxYgC558wrV66st16V+pqeno7mzZurH6cUX3EJJTKrpKam4sWLF+q2V6lduzaA3CnIy5TJvfyyZs2aMDc3h0wmQ5kyZbSyjVTGjBkDPz8/deii0NkIfUnu78Mg12z4+/vj1KlTuHr1KmJiYrBnzx6cOHECJ06cQOvWrQ2xSmJgu3btQmhoKICC0wiFkkKPHDkCuVyu9Uek6eLFi2jcuLHO1NYqAQEBaNGiBWrUqAEbG5sSelelS2xsLDIzM3H8+HFYW1trTRuv2b+ahPrtwYMHCA4Oxv79+/H999/r5KNQXxpH1apVce/ePZw4cQLHjh3D9evXC/W6UaNGoWXLlvD39wcASvHliGYis8rBgwfRoUMHva9ZtWoVunbtqvXYwYMHUa9ePcHB39GjRwUDMYF3p8fnzJmD4ODgYr4LYQYZbKgGFC1atEBUVBSmT5+OkSNHYvjw4Xj58qUhVkkMbN++fejSpQuAgtMIhZJC165di88++0zv8kNCQqBUKnWu51A5fvw4bt26BScnJ5w+ffo9303p5ODgALlcDiD3B8Ht27fVz2n2ryahflMtx8bGBvXr19c5t0t9aRyWlpawsbFBmTJl0LlzZ9y8ebNQr1uxYgXu3LmDWbNmAQCl+HIibyKzyu7du9VHIfK6cOECDhw4oHX908OHDzF//nz10a68li9fjt69e+tcawfk5h6Fh4fjr7/+wtSpU9/j3egyyGDj2rVrAICbN2+idu3acHFxwYYNG6BQKLBhwwZDrJIY0IsXL1C2bFk4OjoCANq2bYuTJ08CEE4jFEoKvXfvHrp164aFCxdiyZIlgomDa9aswaZNm3Dq1Cm920JJocXn6emp/vUbERGBOnXqANDtX01C/aZaTk5ODqKjo+Hs7KzzOupLw0tOTlb//5kzZ/QeSdKkSn21srJSH52kFF/xCSUyA7kX/N6+fVswFC0mJgZffPEFfv31V/WRx+TkZAwaNAjr1q3Te9QwKCgInTt3FjyKpWKIvnyv1NctW7bg/PnzOo+fPHkSK1asgFwuxwcffICBAwciKioKGRkZ6lRJIh179uzROkxXqVIl+Pr6wtvbGzVr1sT48eMB5J8UqjoXvGHDBmRnZ6Nx48Y667G0tMS2bdvQqVMn7Ny5U+u5gIAAyGQyODo60l0MxeTi4gIrKysoFAo4OTmpr3rP27/Au74U6rfJkydj4MCBSEpKwrBhwwSvu6C+NLzTp09j2rRpsLS0hI+PD9zc3AAAPXv2RHh4OCIjIzFp0iR07dpV3Z/jxo3DnTt3kJmZqU7cphRf8QklMnt4eODvv/9Wn+5SUfXljBkzEBsbqz7qcfDgQSxfvhxRUVEYPHgwAGD9+vXqHxWaxo0bh+HDh2PZsmVajy9YsABbtmxBZmam4J1J74NSX/WgyWZMB/WlaaH+NB3Ul6aFUl8JIYQQIhoabBBCCCHEoGiwQQghhBCDyvcC0Vo1a5TaRL7SnHhraqgvTQv1p+mgvjQttWrW0PtcvheIEkIIIYS8LzqNQgghhBCDosEGIYQQQgyKBhuEEEIIMSgabBBCCCHEoGiwQQghhBCD+n/XRMmZbHVkuAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "ax.set_ylabel(\"Size (KiB)\")\n", + "ax.bar(*zip(*file_size_data), align=\"edge\", label=\"Entire Binary\", width=0.8, color=file_size_data_color)\n", + "ax.bar(*zip(*file_text_section), align=\"edge\", label=\".text section\", width=0.75, hatch=\"o\", color=file_text_section_color)\n", + "ax.bar(tuf_no_std_size[0], tuf_no_std_size[1], align=\"edge\", label=\"TUF\", width=0.7, hatch=\"+\", color=tuf_no_std_size_color)\n", + "ax.bar(_size[0], _size[1], align=\"edge\", label=\"BT²X\", width=0.7, hatch=\"//\", color=_size_color)\n", + "ax.bar(libs_size[0], libs_size[1], align=\"edge\", label=\"Libs\", width=0.7, hatch=\"\\\\\\\\\", color=libs_size_color)\n", + "ax.legend(loc=\"upper right\")\n", + "\n", + "\n", + "\n", + "table = plt.table(\n", + " colWidths=[0.25, 0.25, 0.25, 0.25],\n", + " cellText=list(table_data.values()),\n", + " rowLabels=list(table_data.keys()),\n", + " colLabels=x_labels,\n", + ")\n", + "table.auto_set_font_size(False)\n", + "table.set_fontsize(8)\n", + "plt.subplots_adjust(left=0.2, bottom=0.2)\n", + "plt.xticks([])\n", + "fig.subplots_adjust(left=0.18, right=2/1.6, bottom=0.05, top=1.5/1.6, hspace=0.4, wspace=0.3)\n", + "fig.savefig(\"plots/benchmark-size-used-storage.pdf\", bbox_inches='tight')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notes\n", + "\n", + "- The binaries include no business logic and the rest of the size is mostly from the embassy runtime.\n", + "\n", + "## Analysis\n", + "\n", + "- Using TUF in addition to BT²X introduces some overhead but is able to reuse a lot of the dependencies that are already required by BT²X.\n", + "- Overall, the overhead of the verification code is roughly equal to the size of the WiFi drivers. Both together take up roughly 1/4 of the available flash.\n", + "- The rest of the storage can be used for business logic and storage such as files and new updates. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/bt2x-embassy-std/.dockerignore b/bt2x-embassy-std/.dockerignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/bt2x-embassy-std/.dockerignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/bt2x-embassy-std/Cargo.lock b/bt2x-embassy-std/Cargo.lock new file mode 100644 index 0000000..ac0b616 --- /dev/null +++ b/bt2x-embassy-std/Cargo.lock @@ -0,0 +1,1822 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-trait" +version = "0.1.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitfield" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c821a6e124197eb56d907ccc2188eab1038fb919c914f47976e64dd8dbc855d1" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bt2x-embassy-std" +version = "0.1.0" +dependencies = [ + "async-io", + "bt2x-embedded", + "bt2x-ota-common", + "clap", + "critical-section", + "embassy-executor", + "embassy-net", + "embassy-net-ppp", + "embassy-net-tuntap", + "embassy-sync", + "embassy-time", + "embedded-io-adapters", + "embedded-io-async", + "env_logger", + "futures", + "heapless 0.8.0", + "log", + "nix", + "rand_core", + "static_cell", + "tuf-no-std", +] + +[[package]] +name = "bt2x-embedded" +version = "0.1.0" +dependencies = [ + "base64", + "base64ct", + "der", + "ecdsa", + "hex", + "p256", + "pem-rfc7468", + "pkcs8", + "serde", + "serde-json-core", + "sha2", + "signature", + "spki", + "x509-cert", +] + +[[package]] +name = "bt2x-ota-common" +version = "0.1.0" +dependencies = [ + "bitfield", + "embassy-net", + "embassy-time", + "embedded-io-async", + "embedded-storage", + "log", + "sha2", + "tuf-no-std", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.53", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embassy-executor" +version = "0.5.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor-macros", + "embassy-time-driver", + "embassy-time-queue-driver", + "log", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.4.1" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" + +[[package]] +name = "embassy-net" +version = "0.4.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "document-features", + "embassy-net-driver", + "embassy-sync", + "embassy-time", + "embedded-io-async", + "embedded-nal-async", + "heapless 0.8.0", + "log", + "managed", + "smoltcp", +] + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.2.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-net-ppp" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embedded-io-async", + "log", + "ppproto", +] + +[[package]] +name = "embassy-net-tuntap" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "async-io", + "embassy-net-driver", + "libc", + "log", +] + +[[package]] +name = "embassy-sync" +version = "0.6.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-util", + "heapless 0.8.0", + "log", +] + +[[package]] +name = "embassy-time" +version = "0.3.1" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embassy-time-queue-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", + "heapless 0.8.0", + "log", +] + +[[package]] +name = "embassy-time-driver" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-driver" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=a2acb3e3dceddb4752f8fb1c17aa65e1959a2180#a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-adapters" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03facd2b544d24916f312a6026c1b548b8af012f788a554d498afdc8ef9c775" +dependencies = [ + "embedded-io", + "embedded-io-async", + "futures", +] + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "embedded-nal" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a943fad5ed3d3f8a00f1e80f6bba371f1e7f0df28ec38477535eb318dc19cc" +dependencies = [ + "nb 1.1.0", + "no-std-net", +] + +[[package]] +name = "embedded-nal-async" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72229137a4fc12d239b0b7f50f04b30790678da6d782a0f3f1909bf57ec4b759" +dependencies = [ + "embedded-io-async", + "embedded-nal", + "no-std-net", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" + +[[package]] +name = "flagset" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +dependencies = [ + "serde", +] + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "ppproto" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f23bada7fbe9266b2d485b3abefdff31f1d3cc8c77f067165cf23848c9a32" +dependencies = [ + "heapless 0.7.17", + "log", + "num_enum", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9e1ab533c0bc414c34920ec7e5f097101d126ed5eac1a1aac711222e0bbb33" +dependencies = [ + "heapless 0.7.17", + "ryu", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smoltcp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" +dependencies = [ + "bitflags", + "byteorder", + "cfg-if", + "heapless 0.8.0", + "managed", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", + "sha2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_cell" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa6ba4cf83bf80d3eb25f098ea5e790a0a1fcb5e357442259b231e412c2d3ca0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + +[[package]] +name = "tuf-no-std" +version = "0.1.0" +dependencies = [ + "anyhow", + "const-oid", + "der", + "ed25519", + "ed25519-dalek", + "either", + "hash32 0.3.1", + "heapless 0.8.0", + "hex", + "p256", + "pem-rfc7468", + "serde", + "serde-json-core", + "sha2", + "signature", + "spki", + "tuf-no-std-common", + "tuf-no-std-der", + "zeroize", +] + +[[package]] +name = "tuf-no-std-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "const-oid", + "der", + "ed25519", + "ed25519-dalek", + "either", + "hash32 0.3.1", + "hex", + "p256", + "pem-rfc7468", + "serde", + "sha2", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "tuf-no-std-der" +version = "0.1.0" +dependencies = [ + "anyhow", + "const-oid", + "der", + "ed25519", + "ed25519-dalek", + "either", + "hash32 0.3.1", + "hex", + "p256", + "pem-rfc7468", + "serde", + "serde-json-core", + "sha2", + "signature", + "spki", + "tuf-no-std-common", + "zeroize", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/bt2x-embassy-std/Cargo.toml b/bt2x-embassy-std/Cargo.toml new file mode 100644 index 0000000..f3dc830 --- /dev/null +++ b/bt2x-embassy-std/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "bt2x-embassy-std" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +embassy-sync = { version = "0.6.0", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = ["log"] } +embassy-executor = { version = "0.5.0", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = ["task-arena-size-1048576", "arch-std", "executor-thread", "log", "integrated-timers"] } +embassy-time = { version = "0.3.0", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = ["log", "std", ] } +embassy-net = { version = "0.4.0", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = ["std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } +embassy-net-tuntap = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +embassy-net-ppp = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = ["log"] } +embedded-io-async = { version = "0.6.1" } +embedded-io-adapters = { version = "0.6.1", features = ["futures-03"] } +critical-section = { version = "1.1", features = ["std"] } + +async-io = "1.6.0" +env_logger = "0.9.0" +futures = { version = "0.3.17" } +log = "0.4.14" +nix = "0.26.2" +clap = { version = "3.0.0-beta.5", features = ["derive"] } +rand_core = { version = "0.6.3", features = ["std"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2" +bt2x-embedded = { path = "../bt2x/bt2x-embedded" } +bt2x-ota-common = { path = "../bt2x/bt2x-ota-common", features = ["log", "mock_flash"], default-features = false } +tuf-no-std = { path = "../tuf-no-std/tuf-no-std", default-features = false, features = [ + "der", + "ed25519", + "verify", + "ecdsa", + "async", +] } diff --git a/bt2x-embassy-std/LICENSE.txt b/bt2x-embassy-std/LICENSE.txt new file mode 100644 index 0000000..4052ad4 --- /dev/null +++ b/bt2x-embassy-std/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bt2x-embassy-std/deny.toml b/bt2x-embassy-std/deny.toml new file mode 100644 index 0000000..185f3fa --- /dev/null +++ b/bt2x-embassy-std/deny.toml @@ -0,0 +1,238 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "Unicode-DFS-2016", + "BSD-3-Clause", + "0BSD", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/bt2x-embassy-std/readme.md b/bt2x-embassy-std/readme.md new file mode 100644 index 0000000..81fa9e3 --- /dev/null +++ b/bt2x-embassy-std/readme.md @@ -0,0 +1,20 @@ + +# Embassy `std` Example + +This example can be used to debug the updater code on a target that is easier to debug (locally). + +## Setup + +First, create the tap0 interface. You only need to do this once. + +```sh +sudo ip tuntap add name tap0 mode tap user $USER +sudo ip link set tap0 up +sudo ip addr add 192.168.69.100/24 dev tap0 +sudo ip -6 addr add fe80::100/64 dev tap0 +sudo ip -6 addr add fdaa::100/64 dev tap0 +sudo ip -6 route add fe80::/64 dev tap0 +sudo ip -6 route add fdaa::/64 dev tap0 +``` + +Then you can connect to the OTA server if it's running in the docker setup. diff --git a/bt2x-embassy-std/src/main.rs b/bt2x-embassy-std/src/main.rs new file mode 100644 index 0000000..e1a98b0 --- /dev/null +++ b/bt2x-embassy-std/src/main.rs @@ -0,0 +1,161 @@ +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use std::cmp::min; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::PathBuf; +use std::process::exit; + +use embassy_net::{Config, IpEndpoint, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net_tuntap::TunTapDevice; + +use bt2x_embedded::{verify_bt, Digest, Sha256}; +use bt2x_ota_common::net::Transport; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; +use tuf_no_std::utils::TufStorage; +use tuf_no_std::{DateTime, UtcTime}; + +use bt2x_ota_common::flash::mock_flash::ReadNorFlash; + +const TUF_ROOT: &[u8] = include_bytes!("../../build/1.root.der"); + +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// TAP device name + #[clap(long, default_value = "tap0")] + tap: String, + /// use a static IP instead of DHCP + #[clap(long)] + static_ip: bool, +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Init network device + let device = TunTapDevice::new(&opts.tap).unwrap(); + + // Choose between dhcp or static ip + let config = if opts.static_ip { + Config::ipv4_static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + dns_servers: Vec::new(), + gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + }) + } else { + Config::dhcpv4(Default::default()) + }; + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static STACK: StaticCell> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + device, + config, + RESOURCES.init(StackResources::<3>::new()), + seed, + )); + + // Launch network task + spawner.spawn(net_task(stack)).unwrap(); + + let mut storage = TufStorage { + root: TUF_ROOT.try_into().unwrap(), + uncommitted_root: Default::default(), + snapshot: Default::default(), + timestamp: Default::default(), + targets: Default::default(), + }; + + let mut transport = Transport { + network_stack: &stack, + server: IpEndpoint::new("192.168.69.100".parse().unwrap(), 50000), + }; + let update_start = + UtcTime::from_date_time(DateTime::new(2024, 1, 1, 0, 0, 0).unwrap()).unwrap(); + debug!("starting repo update"); + tuf_no_std::update_repo_async(&mut storage, &mut transport, 4, update_start) + .await + .expect("failed to update repo"); + debug!("repo update complete"); + let mut rekor_pem = [0u8; 4096]; + debug!("fetching rekor.pem"); + let rekor_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"rekor.pub", + &mut rekor_pem, + ) + .await + .unwrap(); + debug!("fetching fulcio.pem"); + let mut fulcio_pem = [0u8; 4096]; + let fulcio_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"fulcio.crt.pem", + &mut fulcio_pem, + ) + .await + .unwrap(); + debug!("fetching binary"); + let mut flash = bt2x_ota_common::flash::mock_flash::FsWriter(PathBuf::from("flash-test")); + let (_, hasher) = transport + .fetch_binary_flash::(b"pi-pico-bin", &mut flash) + .await + .unwrap(); + + debug!("fetching signature"); + let mut signature_buf = [0u8; 4096]; + let bundle = transport + .fetch_signature(b"pi-pico-bin.canonical.json", &mut signature_buf) + .await + .unwrap(); + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open("pi-pico-bin.canonical.json") + .unwrap() + .write_all(bundle) + .unwrap(); + debug!("verifying bundle"); + verify_bt( + rekor_pem, + fulcio_pem, + bundle, + hasher, + &[(env!("GITHUB_EMAIL"), "http://dex-idp:8888/")], + ) + .unwrap(); + exit(0) +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + info!("logging set up"); + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} diff --git a/bt2x-embedded-ffi/.cargo/config.toml b/bt2x-embedded-ffi/.cargo/config.toml new file mode 100644 index 0000000..9efee45 --- /dev/null +++ b/bt2x-embedded-ffi/.cargo/config.toml @@ -0,0 +1,16 @@ +rustflags = [ + # "-C", "linker=flip-link", + # "-C", "link-arg=--nmagic", + # "-C", "link-arg=-Tlink.x", + # "-C", "link-arg=-Tdefmt.x", + # Code-size optimizations. + # trap unreachable can save a lot of space, but requires nightly compiler. + # uncomment the next line if you wish to enable it + "-Z", "trap-unreachable=yes", + "-C", "inline-threshold=5", + "-C", "no-vectorize-loops", +] + +[build] +target = "thumbv6m-none-eabi" + diff --git a/bt2x-embedded-ffi/.dockerignore b/bt2x-embedded-ffi/.dockerignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/bt2x-embedded-ffi/.dockerignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/bt2x-embedded-ffi/.gitignore b/bt2x-embedded-ffi/.gitignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/bt2x-embedded-ffi/.gitignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/bt2x-embedded-ffi/Cargo.toml b/bt2x-embedded-ffi/Cargo.toml new file mode 100644 index 0000000..c303a4e --- /dev/null +++ b/bt2x-embedded-ffi/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "bt2x-embedded-ffi" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[profile.dev] +panic = "abort" + +# cargo build/run --release +[profile.release] +panic = "abort" +codegen-units = 1 +debug = false +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = "z" +overflow-checks = false +strip = "symbols" + + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[lib] +crate-type = ["staticlib"] + +[dependencies] +bt2x-embedded = { path = "../bt2x/bt2x-embedded", features = ["no_std"] } +panic-halt = "0.2.0" +embedded-alloc = "0.5.0" +heapless = { version = "0.8" } + +[build-dependencies] +cbindgen = "0.24.0" diff --git a/bt2x-embedded-ffi/LICENSE.txt b/bt2x-embedded-ffi/LICENSE.txt new file mode 100644 index 0000000..4052ad4 --- /dev/null +++ b/bt2x-embedded-ffi/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bt2x-embedded-ffi/bindings.h b/bt2x-embedded-ffi/bindings.h new file mode 100644 index 0000000..1bb89e5 --- /dev/null +++ b/bt2x-embedded-ffi/bindings.h @@ -0,0 +1,20 @@ +#ifndef BT4OT +#define BT4OT + +typedef struct SubjectIdentity { + const char *identifier; + const char *issuer; +} SubjectIdentity; + +bool verify_bt(const uint8_t *rekor_pub_key, + uintptr_t rekor_pub_key_len, + const uint8_t *root_cert, + uintptr_t root_cert_len, + const uint8_t *bundle, + uintptr_t bundle_len, + const uint8_t *blob, + uintptr_t blob_len, + const struct SubjectIdentity *subject_identities, + uintptr_t subject_identities_len); + +#endif /* BT4OT */ diff --git a/bt2x-embedded-ffi/build.rs b/bt2x-embedded-ffi/build.rs new file mode 100644 index 0000000..ab2d4d7 --- /dev/null +++ b/bt2x-embedded-ffi/build.rs @@ -0,0 +1,17 @@ +extern crate cbindgen; + +use cbindgen::Language; +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_no_includes() + .with_language(Language::C) + .with_include_guard("BT4OT") + .with_crate(crate_dir) + .generate() + .expect("Unable to generate bindings") + .write_to_file("bindings.h"); +} diff --git a/bt2x-embedded-ffi/deny.toml b/bt2x-embedded-ffi/deny.toml new file mode 100644 index 0000000..0e169b2 --- /dev/null +++ b/bt2x-embedded-ffi/deny.toml @@ -0,0 +1,237 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "MPL-2.0", + "Unicode-DFS-2016", + "BSD-3-Clause", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/bt2x-embedded-ffi/rust-toolchain.toml b/bt2x-embedded-ffi/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/bt2x-embedded-ffi/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/bt2x-embedded-ffi/src/lib.rs b/bt2x-embedded-ffi/src/lib.rs new file mode 100644 index 0000000..97a5445 --- /dev/null +++ b/bt2x-embedded-ffi/src/lib.rs @@ -0,0 +1,88 @@ +#![no_std] +#![feature(alloc_error_handler)] +#![feature(iterator_try_collect)] +use core::{ + alloc::{GlobalAlloc, Layout}, + ffi::{c_char, CStr}, +}; +use panic_halt as _; + +struct PanicAllocator; + +#[cfg(not(test))] +#[global_allocator] +static GLOBAL: PanicAllocator = PanicAllocator; + +unsafe impl GlobalAlloc for PanicAllocator { + unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { + panic!() + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + panic!() + } +} + +#[alloc_error_handler] +fn foo(_: core::alloc::Layout) -> ! { + panic!() +} + +#[repr(C)] +pub struct SubjectIdentity { + identifier: *const c_char, + issuer: *const c_char, +} + +impl Default for SubjectIdentity { + fn default() -> Self { + Self { + identifier: core::ptr::null(), + issuer: core::ptr::null(), + } + } +} + +#[no_mangle] +pub extern "C" fn verify_bt( + rekor_pub_key: *const u8, + rekor_pub_key_len: usize, + root_cert: *const u8, + root_cert_len: usize, + bundle: *const u8, + bundle_len: usize, + blob: *const u8, + blob_len: usize, + subject_identities: *const SubjectIdentity, + subject_identities_len: usize, +) -> bool { + let rekor_pub_key = unsafe { core::slice::from_raw_parts(rekor_pub_key, rekor_pub_key_len) }; + let root_cert = unsafe { core::slice::from_raw_parts(root_cert, root_cert_len) }; + let bundle = unsafe { core::slice::from_raw_parts(bundle, bundle_len) }; + let blob = unsafe { core::slice::from_raw_parts(blob, blob_len) }; + let subject_identities = + unsafe { core::slice::from_raw_parts(subject_identities, subject_identities_len) }; + let Ok(subject_identities) = subject_identities + .iter() + .map(|s| { + let identifier = unsafe { CStr::from_ptr(s.identifier) }; + let issuer = unsafe { CStr::from_ptr(s.issuer) }; + Ok(( + identifier.to_str().map_err(|_| ())?, + issuer.to_str().map_err(|_| ())?, + )) + }) + .collect::, ()>>() + else { + return false; + }; + + bt2x_embedded::verify_bt( + rekor_pub_key, + root_cert, + bundle, + blob, + subject_identities.as_slice(), + ) + .is_ok() +} diff --git a/bt2x-pi-pico-w/.cargo/config.toml b/bt2x-pi-pico-w/.cargo/config.toml new file mode 100644 index 0000000..07457de --- /dev/null +++ b/bt2x-pi-pico-w/.cargo/config.toml @@ -0,0 +1,30 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# Choose a default "cargo run" tool: +# - probe-run provides flashing and defmt via a hardware debugger +# - cargo embed offers flashing, rtt, defmt and a gdb server via a hardware debugger +# it is configured via the Embed.toml in the root of this project +# - elf2uf2-rs loads firmware over USB when the rp2040 is in boot mode +runner = "probe-rs run --chip RP2040" +#runner = "cargo embed" +# runner = "elf2uf2-rs -d" + +rustflags = [ + #"-C", "linker=flip-link", + #"-C", "link-arg=--nmagic", + #"-C", "link-arg=-Tlink.x", + #"-C", "link-arg=-Tdefmt.x", + + # Code-size optimizations. + # trap unreachable can save a lot of space, but requires nightly compiler. + # uncomment the next line if you wish to enable it + "-Z", "trap-unreachable=yes", + "-C", "inline-threshold=5", + "-C", "no-vectorize-loops", +] + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "debug" + diff --git a/bt2x-pi-pico-w/.dockerignore b/bt2x-pi-pico-w/.dockerignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/bt2x-pi-pico-w/.dockerignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/bt2x-pi-pico-w/.gitignore b/bt2x-pi-pico-w/.gitignore new file mode 100644 index 0000000..b90e600 --- /dev/null +++ b/bt2x-pi-pico-w/.gitignore @@ -0,0 +1,17 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb +.env + diff --git a/bt2x-pi-pico-w/Cargo.toml b/bt2x-pi-pico-w/Cargo.toml new file mode 100644 index 0000000..9b4d381 --- /dev/null +++ b/bt2x-pi-pico-w/Cargo.toml @@ -0,0 +1,177 @@ +[package] +name = "pico-w-updater" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[[bin]] +path = "src/blinky.rs" +name = "blinky" +license = "Apache-2.0" + +[[bin]] +path = "src/benchmarking/code-size-full.rs" +name = "benchmark-code-size-full" +license = "Apache-2.0" + +[[bin]] +path = "src/benchmarking/code-size-no-tuf.rs" +name = "benchmark-code-size-no-tuf" +license = "Apache-2.0" + +[[bin]] +path = "src/benchmarking/code-size-simple-signature.rs" +name = "benchmark-code-size-signature-only" +license = "Apache-2.0" + +[[bin]] +path = "src/benchmarking/code-size-update-only.rs" +name = "benchmark-code-size-update-only" +license = "Apache-2.0" + +[[bin]] +path = "src/benchmarking/performance.rs" +name = "benchmark-performance" +license = "Apache-2.0" + +[dependencies] +embassy-embedded-hal = { version = "0.1", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "defmt", +] } +embassy-sync = { version = "0.6", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "defmt", +] } +embassy-executor = { version = "0.5", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "nightly", + "arch-cortex-m", + "executor-thread", + "executor-interrupt", + "defmt", + "integrated-timers", + "task-arena-size-16384", +] } +embassy-time = { version = "0.3", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "defmt", + "defmt-timestamp-uptime", +] } +embassy-rp = { version = "0.1", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "defmt", + "unstable-pac", + "time-driver", + "critical-section-impl", +] } +embassy-boot-rp = { version = "0.2", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ +] } +embassy-usb = { version = "0.2", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "defmt", +] } +embassy-net = { version = "0.4", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "defmt", + "tcp", + "udp", + "dhcpv4", + "medium-ethernet", + "dns", +] } +embassy-net-wiznet = { version = "0.1", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [ + "defmt", +] } +embassy-futures = { version = "0.1", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +embassy-usb-logger = { version = "0.2", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +cyw43 = { git = "https://github.com/embassy-rs/embassy", features = [ + "defmt", + "firmware-logs", +], rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +cyw43-pio = { git = "https://github.com/embassy-rs/embassy", features = [ + "defmt", + "overclock", +], rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } + +#smoltcp = { version = "0.10.0", default-features = false, features = [ +# "proto-ipv4", +# "socket", +# "async", +#] } + +bt2x-embedded = { path = "../bt2x/bt2x-embedded" } +bt2x-ota-common = { path = "../bt2x/bt2x-ota-common", default-features = false, features = [ + "defmt", +] } +tuf-no-std = { path = "../tuf-no-std/tuf-no-std", default-features = false, features = [ + "der", + "verify", + "ecdsa", + "async", +] } + +embedded-alloc = "0.5.0" +atomic-polyfill = "1.0.3" +ecdsa = { version = "0.16.1", default-features = false } +signature = "2.0.0" +p256 = { version = "0.13.0", default-features = false } +digest = "0.10.6" +serde = { version = "1.0", default-features = false } +serde-json-core = "0.5.0" +reqwless = { version = "0.12.0", default-features = false, features = [ + "defmt", +] } +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = [ + "async-await", + "cfg-target-has-atomic", + "unstable", +] } +byte-slice-cast = { version = "1.2.0", default-features = false } + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.2.0", features = ["async"] } +embedded-io-async = { version = "0.6", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = { version = "2", features = ["nightly"] } +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } + +[patch.crates-io] + + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 1 +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 1 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false +strip = "symbols" + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 1 +debug = false +debug-assertions = false +opt-level = "z" +overflow-checks = false diff --git a/bt2x-pi-pico-w/LICENSE.txt b/bt2x-pi-pico-w/LICENSE.txt new file mode 100644 index 0000000..4052ad4 --- /dev/null +++ b/bt2x-pi-pico-w/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bt2x-pi-pico-w/README.md b/bt2x-pi-pico-w/README.md new file mode 100644 index 0000000..c5de0d4 --- /dev/null +++ b/bt2x-pi-pico-w/README.md @@ -0,0 +1,52 @@ +# BT²X Example on a Pi Pico W + +This folder contains the source code for the Pi Pico W example. The `main.rs` file contains the updater and the `blinky.rs` file contains the dummy program. + +The WiFi drivers are in the `firmware` directory. The binaries used for benchmarking are in the `src/benchmarking` directory. + +## Building/Flashing + +### Environment variables + +The example application requires a network connection via WiFi. + +To configure the network connection create a `.env` file and source it, it has to set the correct environment variables. It has the following variables: +- `WIFI_NETWORK` +- `WIFI_PASSWORD` +- `OTA_SERVER_PORT`: TCP port of the OTA server, set it to `50000`. +- `OTA_SERVER_HOST`: the network address of the host running the docker setup. +- `GITHUB_EMAIL`: the email address that was used to sign the firmware. + + +### Flashing + +If you have [probe-rs](https://probe.rs/docs/getting-started/installation/) installed and the Pico W is correctly wired to a debug probe you can run the following command to flash binaries. + +```sh +cargo flash --release --bin= --chip RP2040 +``` +#### Flashing the Bootloader + +To run the code you need to flash the bootloader, it is put at the beginning of the flash. +The other applications are flashed at addresses that succeed it, refer to respective `memory.x` files for more details.+ + +You only need to run this command **once**, assuming you do not overwrite the part of the flash that stores the bootloader. + +```sh +cargo flash --manifest-path ../pico-w-bootloader/Cargo.toml --release --chip RP2040 +``` + +#### Flashing the Updater + +```sh +cargo flash --release --bin=pico-w-updater --chip RP2040 +``` + +#### WiFi Drivers + +You also need to flash the WiFi drivers once. If you overwrite the addresses where the drivers are stored you need to flash them again. However, this should not happen with the examples from this repository. + +```sh +probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10110000 +probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 +``` \ No newline at end of file diff --git a/bt2x-pi-pico-w/build.rs b/bt2x-pi-pico-w/build.rs new file mode 100644 index 0000000..81539fb --- /dev/null +++ b/bt2x-pi-pico-w/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + // This is commented out so the bootloader is not overwritten + // println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/bt2x-pi-pico-w/deny.toml b/bt2x-pi-pico-w/deny.toml new file mode 100644 index 0000000..dfcec68 --- /dev/null +++ b/bt2x-pi-pico-w/deny.toml @@ -0,0 +1,238 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Unicode-DFS-2016", + "BSD-3-Clause", + "0BSD", + "CC0-1.0", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/bt2x-pi-pico-w/firmware/43439A0.bin b/bt2x-pi-pico-w/firmware/43439A0.bin new file mode 100755 index 0000000000000000000000000000000000000000..b46b3beff05de7dfcbf41d53a12bf3a74381881b GIT binary patch literal 224190 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%0DkO7rHTf}Q17Z4ste}do4Nrd>hUFkyZ${+KA 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

1d=ZlpzNk=JW(zlukh-yUe%sMBuCzTL5c8(n(5 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≫q$>JSAzW#r380%0>2$qMp~GuIgVDDg#c#<>v{ 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 C^%uGT literal 0 HcmV?d00001 diff --git a/bt2x-pi-pico-w/firmware/43439A0_clm.bin b/bt2x-pi-pico-w/firmware/43439A0_clm.bin new file mode 100755 index 0000000000000000000000000000000000000000..6e3ba786b2b496ef3d347d8f8150cb433f684692 GIT binary patch literal 4752 zcmd5=&2Jo85wG`p#$%7i<96cRusN(+3P=bXGc$G^+e9l*_k4HvxINu7p0RR?hy({h zP{bjpAntoY95`}CLIUx3gTWD{Ngg`9-o{YpXg-u9lm+};^mKCzk1v`?tGju z#x%{YX_-CKw$?W7u5Ecc&F1cItJT_TwfFYg?VB#$^qA!@2Si6~`O}pFMxf_WY1-HEpv$?9jST>m~JvroS-# z!t|Fm9GY-!!nuk1Hkz1dW1>4V7OTpeSlh%JosDYhDHoVIZQtQvjtjK%Upyw^K`Zzoyk4?!M zUeaPAo~@PUux3qC!yXgTST^;r#=2_ckuisLTZh`38MVBm?VTMMH%wd4D$Q5hUZ-u2 z+TPWN8Sl#TuIlF4_nchvt}MAl%KHU*VGUM)(elE!_dW2k-nz9#&70RNhaFn7Lu;}yG#Cukq_AP=P=c>b!tjmWgYB^j-*VmK9G731TO7i`2fVNvaeO(op6N!2%E!@3E&C=hes$>IK10sjll6kg~L@qrY6cYWrDro zhcicX!bFsYsn{F>Dg4Y8Dc7y8Sv!D!1QZqfMzw30xV*Zlfd+-Et&G6W$d&;t1Upw@Atbf@fw|o}!6L=5x_W zo=9TT1W5v+E&3--0M#j*A&_Td8!fQBU$7~ z1Oq;y-N+0{;w##iSmij}9)Ky9}dlNl464@Zc-A^TllGw$I5=4i0oS#T^ zDAAyBpX8J#nk3vd$p}gc(uTVzA&`grEGLB-EHL2iN(kBSl#0iKR6Ukr6Crz3t%vDE zvNzS*k|NoV>Z!Ueh3j}b1gS)j(oE`nimbz~OeN>Ym& zQ&9%zT28Ow4TK=;%ht=(tC^lM8MMP2nVvEkyb9N4Y6f?`B1heu%_RicLaI$Bg@`is zAvemxB?qFP$x%YwKs%*;C$Xhv?YNnslc5_au$_gNN(;r#J+~9M&?>PI$i%(4bLK*d zUMH7S4|1eB+>&dm=g2XHJ6CUTAMYgRV{ufjIhJcsxY3t2Gr^u~`tb!orZf+8bySW# zLSf;XgnNz*2 zag_OT(kh!3@sPWI5F2TzmMUi@5u}v{Zlx))(xhEYMe<5e&tVEyr4cJn{C;wX|r8B0moI19|<0yYR|}CZQG+g zd*t(_>B_Um)wi`r{E7qXzVWEotSKPinw8tPHT(8}Z+s#-!^9eFFDo6qgzsSXYR8_l zV8ROpzMB#Knjaiu`T^7b;(Hkfm+-3P1=IXTf8+Nf-0+ge^qL2_HJ~Qb-@Qki*G|Tl z{JTZQdbRpiV|%B$%Qr#mZMXK?`~Jbb``bDU8GqpSCfw6-H^Y6*?1W~gG;&16< literal 0 HcmV?d00001 diff --git a/bt2x-pi-pico-w/firmware/LICENSE-permissive-binary-license-1.0.txt b/bt2x-pi-pico-w/firmware/LICENSE-permissive-binary-license-1.0.txt new file mode 100644 index 0000000..cbb51f9 --- /dev/null +++ b/bt2x-pi-pico-w/firmware/LICENSE-permissive-binary-license-1.0.txt @@ -0,0 +1,49 @@ +Permissive Binary License + +Version 1.0, July 2019 + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +1) Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. + +2) Unless to the extent explicitly permitted by law, no reverse + engineering, decompilation, or disassembly of this software is + permitted. + +3) Redistribution as part of a software development kit must include the + accompanying file named ďż˝DEPENDENCIESďż˝ and any dependencies listed in + that file. + +4) Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +Limited patent license. The copyright holders (and contributors) grant a +worldwide, non-exclusive, no-charge, royalty-free patent license to +make, have made, use, offer to sell, sell, import, and otherwise +transfer this software, where such license applies only to those patent +claims licensable by the copyright holders (and contributors) that are +necessarily infringed by this software. This patent license shall not +apply to any combinations that include this software. No hardware is +licensed hereunder. + +If you institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the software +itself infringes your patent(s), then your rights granted under this +license shall terminate as of the date such litigation is filed. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/bt2x-pi-pico-w/firmware/README.md b/bt2x-pi-pico-w/firmware/README.md new file mode 100644 index 0000000..7381fdc --- /dev/null +++ b/bt2x-pi-pico-w/firmware/README.md @@ -0,0 +1,5 @@ +# WiFi firmware + +Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439 + +Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) \ No newline at end of file diff --git a/bt2x-pi-pico-w/memory.x b/bt2x-pi-pico-w/memory.x new file mode 100644 index 0000000..2f8434b --- /dev/null +++ b/bt2x-pi-pico-w/memory.x @@ -0,0 +1,27 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K + FLASH : ORIGIN = 0x10007000, LENGTH = 512K + DFU : ORIGIN = 0x10087000, LENGTH = 516K + + /* Pick one of the two options for RAM layout */ + + /* OPTION A: Use all RAM banks as one big block */ + /* Reasonable, unless you are doing something */ + /* really particular with DMA or other concurrent */ + /* access that would benefit from striping */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K + + /* OPTION B: Keep the unstriped sections separate */ + /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ + /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ + /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOT2); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOT2); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOT2); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOT2); diff --git a/bt2x-pi-pico-w/random-bytes b/bt2x-pi-pico-w/random-bytes new file mode 100644 index 0000000..8ba68e0 --- /dev/null +++ b/bt2x-pi-pico-w/random-bytes @@ -0,0 +1,3 @@ +fill this file with random bytes as needed +e.g. with: +dd if=/dev/urandom of=random-bytes bs=64K count=4 \ No newline at end of file diff --git a/bt2x-pi-pico-w/rust-toolchain.toml b/bt2x-pi-pico-w/rust-toolchain.toml new file mode 100644 index 0000000..c5d68c3 --- /dev/null +++ b/bt2x-pi-pico-w/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-06-18" +components = ["rust-src", "rustfmt", "llvm-tools", "miri"] +targets = ["thumbv6m-none-eabi"] diff --git a/bt2x-pi-pico-w/src/benchmarking/code-size-full.rs b/bt2x-pi-pico-w/src/benchmarking/code-size-full.rs new file mode 100644 index 0000000..d90bc5c --- /dev/null +++ b/bt2x-pi-pico-w/src/benchmarking/code-size-full.rs @@ -0,0 +1,253 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![feature(impl_trait_in_assoc_type)] +#![allow(stable_features, unknown_lints, async_fn_in_trait)] + +use bt2x_embedded::{verify_bt, Sha256}; +use bt2x_ota_common::net::Transport; +use cyw43_pio::PioSpi; +// use defmt::*; +use embassy_boot_rp::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_net::{Config, IpEndpoint, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Timer}; +use rand::RngCore; +//use embedded_io_async::Write as AsyncIoWrite; +use core::cell::RefCell; +use static_cell::StaticCell; +use tuf_no_std::utils::MemoryStorage; +use tuf_no_std::{DateTime, UtcTime}; +use {defmt_rtt as _, panic_probe as _}; + +use embedded_alloc::Heap; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +// Dummy values +const WIFI_NETWORK: &str = "the network"; +const WIFI_PASSWORD: &str = "the password"; +const OTA_SERVER_PORT: &str = "5000"; +const OTA_SERVER_HOST: &str = "127.0.0.1"; +const TUF_ROOT: &[u8] = b""; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + let mut rng = RoscRng; + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + //let fw = include_bytes!("../firmware/43439A0.bin"); + //let clm = include_bytes!("../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10110000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 + let fw = unsafe { core::slice::from_raw_parts(0x10110000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x10150000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + spawner.spawn(wifi_task(runner)).unwrap(); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + watchdog.feed(); + let config = Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + let seed = rng.next_u64(); + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + net_device, + config, + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + spawner.spawn(net_task(stack)).unwrap(); + watchdog.feed(); + loop { + //control.join_open(WIFI_NETWORK).await; + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + //info!("waiting for DHCP..."); + while !stack.is_config_up() { + control.gpio_set(0, true).await; + Timer::after_millis(20).await; + watchdog.feed(); + control.gpio_set(0, false).await; + } + //info!("DHCP is now up!"); + + let mut storage = MemoryStorage { + root: TUF_ROOT.try_into().unwrap(), + uncommitted_root: Default::default(), + snapshot: Default::default(), + timestamp: Default::default(), + targets: Default::default(), + }; + + let mut transport = Transport { + network_stack: stack, + server: IpEndpoint::new( + OTA_SERVER_HOST.parse().unwrap(), + OTA_SERVER_PORT.parse().unwrap(), + ), + }; + watchdog.feed(); + + let update_start = + UtcTime::from_date_time(DateTime::new(2024, 1, 1, 0, 0, 0).unwrap()).unwrap(); + tuf_no_std::update_repo_async(&mut storage, &mut transport, 4, update_start) + .await + .expect("failed to update repo"); + + watchdog.feed(); + let mut rekor_pem = [0u8; 4096]; + let rekor_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"rekor.pub", + &mut rekor_pem, + ) + .await + .unwrap(); + watchdog.feed(); + let mut fulcio_pem = [0u8; 4096]; + let fulcio_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"fulcio.crt.pem", + &mut fulcio_pem, + ) + .await + .unwrap(); + watchdog.feed(); + let mut bundle = [0u8; 4096]; + let bundle = transport + .fetch_signature(b"pi-pico-bin.canonical.json", &mut bundle) + .await + .unwrap(); + watchdog.feed(); + + let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut aligned = AlignedBuffer([0; 1]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); + let mut dfu = updater.prepare_update().unwrap(); + watchdog.feed(); + + control.gpio_set(0, true).await; + let (_, hasher) = transport + .fetch_binary_flash::(b"pi-pico-bin", &mut dfu) + .await + .unwrap(); + control.gpio_set(0, false).await; + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + + match verify_bt( + rekor_pem, + fulcio_pem, + bundle, + hasher, + &[(env!("GITHUB_EMAIL"), "http://dex-idp:8888/")], + ) { + Ok(_) => {} + Err(_) => loop { + watchdog.feed(); + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + Timer::after_millis(100).await; + control.gpio_set(0, false).await; + }, + } + watchdog.feed(); + control.gpio_set(0, true).await; + let Ok(_) = updater.mark_updated() else { + loop { + watchdog.feed(); + Timer::after_millis(200).await; + control.gpio_set(0, true).await; + Timer::after_millis(200).await; + control.gpio_set(0, false).await; + } + }; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + control.gpio_set(0, false).await; + cortex_m::peripheral::SCB::sys_reset(); +} + +#[global_allocator] +static HEAP: Heap = Heap::empty(); diff --git a/bt2x-pi-pico-w/src/benchmarking/code-size-no-tuf.rs b/bt2x-pi-pico-w/src/benchmarking/code-size-no-tuf.rs new file mode 100644 index 0000000..f7cdf9b --- /dev/null +++ b/bt2x-pi-pico-w/src/benchmarking/code-size-no-tuf.rs @@ -0,0 +1,216 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![feature(impl_trait_in_assoc_type)] +#![allow(stable_features, unknown_lints, async_fn_in_trait)] + +use bt2x_embedded::{verify_bt, Sha256}; +use bt2x_ota_common::net::Transport; +use cyw43_pio::PioSpi; +// use defmt::*; +use embassy_boot_rp::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_net::{Config, IpEndpoint, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Timer}; +use rand::RngCore; +//use embedded_io_async::Write as AsyncIoWrite; +use core::cell::RefCell; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +use embedded_alloc::Heap; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +// Dummy values +const WIFI_NETWORK: &str = "the network"; +const WIFI_PASSWORD: &str = "the password"; +const OTA_SERVER_PORT: &str = "5000"; +const OTA_SERVER_HOST: &str = "127.0.0.1"; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + //let fw = include_bytes!("../firmware/43439A0.bin"); + //let clm = include_bytes!("../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10110000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 + let fw = unsafe { core::slice::from_raw_parts(0x10110000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x10150000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + spawner.spawn(wifi_task(runner)).unwrap(); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + watchdog.feed(); + let config = Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + let mut rng = RoscRng; + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + net_device, + config, + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + spawner.spawn(net_task(stack)).unwrap(); + watchdog.feed(); + loop { + //control.join_open(WIFI_NETWORK).await; + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + //info!("waiting for DHCP..."); + while !stack.is_config_up() { + control.gpio_set(0, true).await; + Timer::after_millis(20).await; + watchdog.feed(); + control.gpio_set(0, false).await; + } + //info!("DHCP is now up!"); + + let transport = Transport { + network_stack: stack, + server: IpEndpoint::new( + OTA_SERVER_HOST.parse().unwrap(), + OTA_SERVER_PORT.parse().unwrap(), + ), + }; + + watchdog.feed(); + let mut bundle = [0u8; 4096]; + let bundle = transport + .fetch_signature(b"pi-pico-bin.canonical.json", &mut bundle) + .await + .unwrap(); + watchdog.feed(); + + let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut aligned = AlignedBuffer([0; 1]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); + let mut dfu = updater.prepare_update().unwrap(); + watchdog.feed(); + + control.gpio_set(0, true).await; + let (_, hasher) = transport + .fetch_binary_flash::(b"pi-pico-bin", &mut dfu) + .await + .unwrap(); + control.gpio_set(0, false).await; + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + + match verify_bt( + b"", + b"", + bundle, + hasher, + &[(env!("GITHUB_EMAIL"), "http://dex-idp:8888/")], + ) { + Ok(_) => {} + Err(_) => loop { + watchdog.feed(); + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + Timer::after_millis(100).await; + control.gpio_set(0, false).await; + }, + } + watchdog.feed(); + control.gpio_set(0, true).await; + let Ok(_) = updater.mark_updated() else { + loop { + watchdog.feed(); + Timer::after_millis(200).await; + control.gpio_set(0, true).await; + Timer::after_millis(200).await; + control.gpio_set(0, false).await; + } + }; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + control.gpio_set(0, false).await; + cortex_m::peripheral::SCB::sys_reset(); +} + +#[global_allocator] +static HEAP: Heap = Heap::empty(); diff --git a/bt2x-pi-pico-w/src/benchmarking/code-size-simple-signature.rs b/bt2x-pi-pico-w/src/benchmarking/code-size-simple-signature.rs new file mode 100644 index 0000000..70d1d08 --- /dev/null +++ b/bt2x-pi-pico-w/src/benchmarking/code-size-simple-signature.rs @@ -0,0 +1,204 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![feature(impl_trait_in_assoc_type)] +#![allow(stable_features, unknown_lints, async_fn_in_trait)] + +use bt2x_embedded::Sha256; +use bt2x_ota_common::net::Transport; +use cyw43_pio::PioSpi; +// use defmt::*; +use embassy_boot_rp::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_net::{Config, IpEndpoint, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Timer}; +use p256::pkcs8::DecodePublicKey; +use rand::RngCore; +use signature::DigestVerifier; +//use embedded_io_async::Write as AsyncIoWrite; +use core::cell::RefCell; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +use embedded_alloc::Heap; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +// Dummy values +const WIFI_NETWORK: &str = "the network"; +const WIFI_PASSWORD: &str = "the password"; +const OTA_SERVER_PORT: &str = "5000"; +const OTA_SERVER_HOST: &str = "127.0.0.1"; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + //let fw = include_bytes!("../firmware/43439A0.bin"); + //let clm = include_bytes!("../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10110000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 + let fw = unsafe { core::slice::from_raw_parts(0x10110000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x10150000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + spawner.spawn(wifi_task(runner)).unwrap(); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + watchdog.feed(); + let config = Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + let mut rng = RoscRng; + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + net_device, + config, + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + spawner.spawn(net_task(stack)).unwrap(); + watchdog.feed(); + loop { + //control.join_open(WIFI_NETWORK).await; + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + //info!("waiting for DHCP..."); + while !stack.is_config_up() { + control.gpio_set(0, true).await; + Timer::after_millis(20).await; + watchdog.feed(); + control.gpio_set(0, false).await; + } + + let transport = Transport { + network_stack: stack, + server: IpEndpoint::new( + OTA_SERVER_HOST.parse().unwrap(), + OTA_SERVER_PORT.parse().unwrap(), + ), + }; + + watchdog.feed(); + let mut bundle = [0u8; 4096]; + // Left in, we assume this has the same code complexity as fetching a regular + let _ = transport + .fetch_signature(b"pi-pico-bin.canonical.json", &mut bundle) + .await + .unwrap(); + watchdog.feed(); + + let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut aligned = AlignedBuffer([0; 1]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); + let mut dfu = updater.prepare_update().unwrap(); + watchdog.feed(); + + control.gpio_set(0, true).await; + let (_, hasher) = transport + .fetch_binary_flash::(b"pi-pico-bin", &mut dfu) + .await + .unwrap(); + + // Dummy verification + let verifying_key = p256::ecdsa::VerifyingKey::from_public_key_der(&[]).unwrap(); + let signature = p256::ecdsa::DerSignature::from_bytes(&[]).unwrap(); + verifying_key.verify_digest(hasher, &signature).unwrap(); + + watchdog.feed(); + control.gpio_set(0, true).await; + let Ok(_) = updater.mark_updated() else { + loop { + watchdog.feed(); + Timer::after_millis(200).await; + control.gpio_set(0, true).await; + Timer::after_millis(200).await; + control.gpio_set(0, false).await; + } + }; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + control.gpio_set(0, false).await; + cortex_m::peripheral::SCB::sys_reset(); +} + +#[global_allocator] +static HEAP: Heap = Heap::empty(); diff --git a/bt2x-pi-pico-w/src/benchmarking/code-size-update-only.rs b/bt2x-pi-pico-w/src/benchmarking/code-size-update-only.rs new file mode 100644 index 0000000..f56cf48 --- /dev/null +++ b/bt2x-pi-pico-w/src/benchmarking/code-size-update-only.rs @@ -0,0 +1,193 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![feature(impl_trait_in_assoc_type)] +#![allow(stable_features, unknown_lints, async_fn_in_trait)] + +use bt2x_embedded::Sha256; +use bt2x_ota_common::net::Transport; +use cyw43_pio::PioSpi; +// use defmt::*; +use embassy_boot_rp::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_net::{Config, IpEndpoint, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Timer}; +use rand::RngCore; +//use embedded_io_async::Write as AsyncIoWrite; +use core::cell::RefCell; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +use embedded_alloc::Heap; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +// Dummy values +const WIFI_NETWORK: &str = "the network"; +const WIFI_PASSWORD: &str = "the password"; +const OTA_SERVER_PORT: &str = "5000"; +const OTA_SERVER_HOST: &str = "127.0.0.1"; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + //let fw = include_bytes!("../firmware/43439A0.bin"); + //let clm = include_bytes!("../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10110000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 + let fw = unsafe { core::slice::from_raw_parts(0x10110000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x10150000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + spawner.spawn(wifi_task(runner)).unwrap(); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + watchdog.feed(); + let config = Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + let mut rng = RoscRng; + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + net_device, + config, + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + spawner.spawn(net_task(stack)).unwrap(); + watchdog.feed(); + loop { + //control.join_open(WIFI_NETWORK).await; + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + //info!("waiting for DHCP..."); + while !stack.is_config_up() { + control.gpio_set(0, true).await; + Timer::after_millis(20).await; + watchdog.feed(); + control.gpio_set(0, false).await; + } + + let transport = Transport { + network_stack: stack, + server: IpEndpoint::new( + OTA_SERVER_HOST.parse().unwrap(), + OTA_SERVER_PORT.parse().unwrap(), + ), + }; + + watchdog.feed(); + + let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut aligned = AlignedBuffer([0; 1]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); + let mut dfu = updater.prepare_update().unwrap(); + watchdog.feed(); + + control.gpio_set(0, true).await; + let (_, _) = transport + .fetch_binary_flash::(b"pi-pico-bin", &mut dfu) + .await + .unwrap(); + control.gpio_set(0, false).await; + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + + watchdog.feed(); + control.gpio_set(0, true).await; + let Ok(_) = updater.mark_updated() else { + loop { + watchdog.feed(); + Timer::after_millis(200).await; + control.gpio_set(0, true).await; + Timer::after_millis(200).await; + control.gpio_set(0, false).await; + } + }; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + control.gpio_set(0, false).await; + cortex_m::peripheral::SCB::sys_reset(); +} + +#[global_allocator] +static HEAP: Heap = Heap::empty(); diff --git a/bt2x-pi-pico-w/src/benchmarking/performance.rs b/bt2x-pi-pico-w/src/benchmarking/performance.rs new file mode 100644 index 0000000..1748519 --- /dev/null +++ b/bt2x-pi-pico-w/src/benchmarking/performance.rs @@ -0,0 +1,338 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![feature(impl_trait_in_assoc_type)] +#![allow(stable_features, unknown_lints, async_fn_in_trait)] + +use bt2x_embedded::{verify_bt, Sha256}; +use bt2x_ota_common::net::Transport; +use cyw43_pio::PioSpi; +// use defmt::*; +use embassy_boot_rp::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{IpEndpoint, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Instant, Timer}; +use rand::RngCore; +use reqwless::request::{Method, RequestBuilder}; +//use embedded_io_async::Write as AsyncIoWrite; +use core::cell::RefCell; +use static_cell::StaticCell; +use tuf_no_std::utils::MemoryStorage; +use tuf_no_std::{DateTime, UtcTime}; +use {defmt_rtt as _, panic_probe as _}; + +use embedded_alloc::Heap; + +use p256::pkcs8::DecodePublicKey; +use signature::DigestVerifier; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = env!("WIFI_NETWORK"); +const WIFI_PASSWORD: &str = env!("WIFI_PASSWORD"); +const OTA_SERVER_PORT: &str = env!("OTA_SERVER_PORT"); +const OTA_SERVER_HOST: &str = env!("OTA_SERVER_HOST"); +const TUF_ROOT: &[u8] = include_bytes!("../../../build/1.root.der"); +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +const DUMMY_KEY: &[u8] = include_bytes!("key.der"); +const DUMMY_SIGNATURE: &[u8] = include_bytes!("sig.der"); + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + //let fw = include_bytes!("../firmware/43439A0.bin"); + //let clm = include_bytes!("../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10110000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 + let fw = unsafe { core::slice::from_raw_parts(0x10110000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x10150000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + spawner.spawn(wifi_task(runner)).unwrap(); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + watchdog.feed(); + //let config = Config::dhcpv4(Default::default()); + let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 0, 100), 24), + dns_servers: Default::default(), + gateway: Some(Ipv4Address::new(192, 168, 0, 1)), + }); + + let mut rng = RoscRng; + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + net_device, + config, + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + spawner.spawn(net_task(stack)).unwrap(); + watchdog.feed(); + loop { + //control.join_open(WIFI_NETWORK).await; + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + //info!("waiting for DHCP..."); + while !stack.is_config_up() { + control.gpio_set(0, true).await; + Timer::after_millis(20).await; + watchdog.feed(); + control.gpio_set(0, false).await; + } + + let mut storage = MemoryStorage { + root: TUF_ROOT.try_into().unwrap(), + uncommitted_root: Default::default(), + snapshot: Default::default(), + timestamp: Default::default(), + targets: Default::default(), + }; + + let mut transport = Transport { + network_stack: &stack, + server: IpEndpoint::new( + OTA_SERVER_HOST.parse().unwrap(), + OTA_SERVER_PORT.parse().unwrap(), + ), + }; + watchdog.feed(); + + let update_start = + UtcTime::from_date_time(DateTime::new(2024, 1, 1, 0, 0, 0).unwrap()).unwrap(); + let start_total = Instant::now(); + tuf_no_std::update_repo_async(&mut storage, &mut transport, 4, update_start) + .await + .expect("failed to update repo"); + let duration_update_repo = start_total.elapsed(); + watchdog.feed(); + + let start_fetch_sigstore_keys = Instant::now(); + let mut rekor_pem = [0u8; 4096]; + let rekor_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"rekor.pub", + &mut rekor_pem, + ) + .await + .unwrap(); + watchdog.feed(); + let mut fulcio_pem = [0u8; 4096]; + let fulcio_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"fulcio.crt.pem", + &mut fulcio_pem, + ) + .await + .unwrap(); + let duration_fetch_sigstore_keys = start_fetch_sigstore_keys.elapsed(); + + watchdog.feed(); + let start_fetch_signature = Instant::now(); + let mut bundle = [0u8; 4096]; + let bundle = transport + .fetch_signature(b"pi-pico-bin.canonical.json", &mut bundle) + .await + .unwrap(); + let duration_fetch_signature = start_fetch_signature.elapsed(); + + watchdog.feed(); + + let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut aligned = AlignedBuffer([0; 1]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); + let mut dfu = updater.prepare_update().unwrap(); + + control.gpio_set(0, true).await; + let start_fetch_binary = Instant::now(); + watchdog.feed(); + let (n, hasher) = transport + .fetch_binary_flash::(b"pi-pico-bin", &mut dfu) + .await + .unwrap(); + watchdog.feed(); + let duration_fetch_binary = start_fetch_binary.elapsed(); + watchdog.feed(); + + let start_verify_binary = Instant::now(); + match verify_bt( + rekor_pem, + fulcio_pem, + bundle, + hasher.clone(), + &[(env!("GITHUB_EMAIL"), "http://dex-idp:8888/")], + ) { + Ok(_) => {} + Err(err) => loop { + watchdog.feed(); + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + Timer::after_millis(100).await; + control.gpio_set(0, false).await; + }, + } + let duration_verify_binary = start_verify_binary.elapsed(); + watchdog.feed(); + + let start_verification_naive = Instant::now(); + let verifying_key = p256::ecdsa::VerifyingKey::from_public_key_der(DUMMY_KEY).unwrap(); + let signature = p256::ecdsa::DerSignature::from_bytes(DUMMY_SIGNATURE).unwrap(); + let _ = verifying_key.verify_digest(hasher, &signature); // result does not matter for performance + let duration_verification_naive = start_verification_naive.elapsed(); + watchdog.feed(); + + let end = Instant::now(); + let benchmark_data = BenchmarkData { + start_time: Some(start_total.as_ticks()), + end_time: Some(end.as_ticks()), + binary_size: Some(n), + fetch_target_files: Some(duration_fetch_sigstore_keys.into()), + update_repo: Some(duration_update_repo.into()), + fetch_signature: Some(duration_fetch_signature.into()), + fetch_binary: Some(duration_fetch_binary.into()), + verify_binary: Some(duration_verify_binary.into()), + verify_binary_naive: Some(duration_verification_naive.into()), + }; + watchdog.feed(); + let mut serialize_buf = [0u8; 2048]; + let n = serde_json_core::to_slice(&benchmark_data, &mut serialize_buf) + .expect("failed to serialize"); + watchdog.feed(); + let data = &serialize_buf[..n]; + let client_state = TcpClientState::<1, 1024, 1024>::new(); + let tcp_client = TcpClient::new(stack, &client_state); + let dns_client = embassy_net::dns::DnsSocket::new(stack); + let mut http_client = reqwless::client::HttpClient::new(&tcp_client, &dns_client); + let url = concat!("http://", env!("OTA_SERVER_HOST"), ":8080/"); + // for non-TLS requests, use this instead: + // let mut http_client = HttpClient::new(&tcp_client, &dns_client); + // let url = "http://worldtimeapi.org/api/timezone/Europe/Berlin"; + watchdog.feed(); + let mut request = match http_client.request(Method::POST, &url).await { + Ok(req) => req, + Err(e) => { + return; // handle the error + } + }; + watchdog.feed(); + let mut rx_buffer = [0; 8192]; + let response = request + .body(data) + .send(&mut rx_buffer) + .await + .unwrap() + .body() + .read_to_end() + .await + .unwrap(); + + watchdog.feed(); + control.gpio_set(0, true).await; + let Ok(_) = updater.mark_updated() else { + loop { + watchdog.feed(); + Timer::after_millis(200).await; + control.gpio_set(0, true).await; + Timer::after_millis(200).await; + control.gpio_set(0, false).await; + } + }; + + Timer::after_millis(3000).await; + watchdog.feed(); + Timer::after_millis(3000).await; + watchdog.feed(); + control.gpio_set(0, false).await; + cortex_m::peripheral::SCB::sys_reset(); +} + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] +struct BenchmarkData { + start_time: Option, + end_time: Option, + binary_size: Option, + fetch_target_files: Option, + update_repo: Option, + fetch_signature: Option, + fetch_binary: Option, + verify_binary: Option, + verify_binary_naive: Option, +} diff --git a/bt2x-pi-pico-w/src/blinky.rs b/bt2x-pi-pico-w/src/blinky.rs new file mode 100644 index 0000000..8f4b5bb --- /dev/null +++ b/bt2x-pi-pico-w/src/blinky.rs @@ -0,0 +1,86 @@ +//! This example test the RP Pico W on board LED. +//! +//! It does not work with the RP Pico board. See blinky.rs. + +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] + +use core::hint::black_box; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +const RANDOM_BYTES: &[u8] = include_bytes!("../random-bytes"); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + //let fw = include_bytes!("../firmware/43439A0.bin"); + //let clm = include_bytes!("../firmware/43439A0_clm.bin"); + + //let mut watchdog = Watchdog::new(p.WATCHDOG); + //watchdog.start(Duration::from_secs(8)); + //watchdog.feed(); + //Timer::after_secs(5).await; + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10108000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 + let fw = unsafe { core::slice::from_raw_parts(0x10110000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x10150000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + black_box(RANDOM_BYTES); + let delay = Duration::from_millis(100); + loop { + //watchdog.feed(); + info!("led on!"); + control.gpio_set(0, true).await; + Timer::after(delay).await; + + info!("led off!"); + control.gpio_set(0, false).await; + Timer::after(delay).await; + } +} diff --git a/bt2x-pi-pico-w/src/main.rs b/bt2x-pi-pico-w/src/main.rs new file mode 100644 index 0000000..6ebcb82 --- /dev/null +++ b/bt2x-pi-pico-w/src/main.rs @@ -0,0 +1,252 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![feature(impl_trait_in_assoc_type)] +#![allow(stable_features, unknown_lints, async_fn_in_trait)] + +use bt2x_embedded::{verify_bt, Sha256}; +use bt2x_ota_common::net::Transport; +use cyw43_pio::PioSpi; +// use defmt::*; +use embassy_boot_rp::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_net::{IpEndpoint, Ipv4Address, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Timer}; +use rand::RngCore; +//use embedded_io_async::Write as AsyncIoWrite; +use core::cell::RefCell; +use static_cell::StaticCell; +use tuf_no_std::utils::MemoryStorage; +use tuf_no_std::{DateTime, UtcTime}; +use {defmt_rtt as _, panic_probe as _}; + +use embedded_alloc::Heap; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = env!("WIFI_NETWORK"); +const WIFI_PASSWORD: &str = env!("WIFI_PASSWORD"); +const OTA_SERVER_PORT: &str = env!("OTA_SERVER_PORT"); +const OTA_SERVER_HOST: &str = env!("OTA_SERVER_HOST"); +const TUF_ROOT: &[u8] = include_bytes!("../../build/1.root.der"); +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + let mut rng = RoscRng; + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + //let fw = include_bytes!("../firmware/43439A0.bin"); + //let clm = include_bytes!("../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10110000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10150000 + let fw = unsafe { core::slice::from_raw_parts(0x10110000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x10150000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + spawner.spawn(wifi_task(runner)).unwrap(); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + watchdog.feed(); + //let config = Config::dhcpv4(Default::default()); + let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new(embassy_net::Ipv4Address::new(192, 168, 0, 100), 24), + dns_servers: Default::default(), + gateway: Some(Ipv4Address::new(192, 168, 0, 1)), + }); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + net_device, + config, + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + spawner.spawn(net_task(stack)).unwrap(); + watchdog.feed(); + loop { + //control.join_open(WIFI_NETWORK).await; + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + //info!("waiting for DHCP..."); + while !stack.is_config_up() { + control.gpio_set(0, true).await; + Timer::after_millis(20).await; + watchdog.feed(); + control.gpio_set(0, false).await; + } + + let mut storage = MemoryStorage { + root: TUF_ROOT.try_into().unwrap(), + uncommitted_root: Default::default(), + snapshot: Default::default(), + timestamp: Default::default(), + targets: Default::default(), + }; + + let mut transport = Transport { + network_stack: &stack, + server: IpEndpoint::new( + OTA_SERVER_HOST.parse().unwrap(), + OTA_SERVER_PORT.parse().unwrap(), + ), + }; + watchdog.feed(); + + let update_start = + UtcTime::from_date_time(DateTime::new(2024, 1, 1, 0, 0, 0).unwrap()).unwrap(); + tuf_no_std::update_repo_async(&mut storage, &mut transport, 4, update_start) + .await + .expect("failed to update repo"); + + watchdog.feed(); + let mut rekor_pem = [0u8; 4096]; + let rekor_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"rekor.pub", + &mut rekor_pem, + ) + .await + .unwrap(); + watchdog.feed(); + let mut fulcio_pem = [0u8; 4096]; + let fulcio_pem = tuf_no_std::fetch_and_verify_target_file_async( + &mut storage, + &mut transport, + b"fulcio.crt.pem", + &mut fulcio_pem, + ) + .await + .unwrap(); + watchdog.feed(); + let mut bundle = [0u8; 4096]; + let bundle = transport + .fetch_signature(b"pi-pico-bin.canonical.json", &mut bundle) + .await + .unwrap(); + watchdog.feed(); + + let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut aligned = AlignedBuffer([0; 1]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); + let mut dfu = updater.prepare_update().unwrap(); + watchdog.feed(); + + control.gpio_set(0, true).await; + let (_, hasher) = transport + .fetch_binary_flash::(b"pi-pico-bin", &mut dfu) + .await + .unwrap(); + control.gpio_set(0, false).await; + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + + match verify_bt( + rekor_pem, + fulcio_pem, + bundle, + hasher, + &[(env!("GITHUB_EMAIL"), "http://dex-idp:8888/")], + ) { + Ok(_) => {} + Err(_) => loop { + watchdog.feed(); + Timer::after_millis(100).await; + control.gpio_set(0, true).await; + Timer::after_millis(100).await; + control.gpio_set(0, false).await; + }, + } + watchdog.feed(); + control.gpio_set(0, true).await; + let Ok(_) = updater.mark_updated() else { + loop { + watchdog.feed(); + Timer::after_millis(200).await; + control.gpio_set(0, true).await; + Timer::after_millis(200).await; + control.gpio_set(0, false).await; + } + }; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + Timer::after_millis(2000).await; + watchdog.feed(); + control.gpio_set(0, false).await; + cortex_m::peripheral::SCB::sys_reset(); +} + +#[global_allocator] +static HEAP: Heap = Heap::empty(); diff --git a/bt2x-pi-pico/.cargo/config.toml b/bt2x-pi-pico/.cargo/config.toml new file mode 100644 index 0000000..75a050d --- /dev/null +++ b/bt2x-pi-pico/.cargo/config.toml @@ -0,0 +1,29 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# Choose a default "cargo run" tool: +# - probe-run provides flashing and defmt via a hardware debugger +# - cargo embed offers flashing, rtt, defmt and a gdb server via a hardware debugger +# it is configured via the Embed.toml in the root of this project +# - elf2uf2-rs loads firmware over USB when the rp2040 is in boot mode +# runner = "probe-run --chip RP2040" +# runner = "cargo embed" +runner = "elf2uf2-rs -d" + +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + + # Code-size optimizations. + # trap unreachable can save a lot of space, but requires nightly compiler. + # uncomment the next line if you wish to enable it + "-Z", "trap-unreachable=no", + "-C", "inline-threshold=5", + "-C", "no-vectorize-loops", +] + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "debug" diff --git a/bt2x-pi-pico/.dockerignore b/bt2x-pi-pico/.dockerignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/bt2x-pi-pico/.dockerignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/bt2x-pi-pico/.github/workflows/ci_checks.yml b/bt2x-pi-pico/.github/workflows/ci_checks.yml new file mode 100644 index 0000000..b397f4b --- /dev/null +++ b/bt2x-pi-pico/.github/workflows/ci_checks.yml @@ -0,0 +1,66 @@ +name: CI Checks + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + building: + name: Building + continue-on-error: ${{ matrix.experimental || false }} + strategy: + matrix: + # All generated code should be running on stable now + rust: [nightly, stable] + include: + # Nightly is only for reference and allowed to fail + - rust: nightly + experimental: true + os: + # Check compilation works on common OSes + # (i.e. no path issues) + - ubuntu-latest + - macOS-latest + - windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo install flip-link + - run: rustup target install --toolchain=${{ matrix.rust }} thumbv6m-none-eabi + - run: cargo build --all + - run: cargo build --all --release + linting: + name: Linting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + - run: rustup target install thumbv6m-none-eabi + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features -- -D warnings + formatting: + name: Formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + - run: rustup target install thumbv6m-none-eabi + - run: cargo fmt -- --check diff --git a/bt2x-pi-pico/.gitignore b/bt2x-pi-pico/.gitignore new file mode 100644 index 0000000..3a0e903 --- /dev/null +++ b/bt2x-pi-pico/.gitignore @@ -0,0 +1,14 @@ +**/*.rs.bk +.#* +.gdb_history +Cargo.lock +target/ + +# editor files +.vscode/* +!.vscode/*.md +!.vscode/*.svd +!.vscode/launch.json +!.vscode/tasks.json +!.vscode/extensions.json +!.vscode/settings.json \ No newline at end of file diff --git a/bt2x-pi-pico/.vscode/launch.json b/bt2x-pi-pico/.vscode/launch.json new file mode 100644 index 0000000..45a2207 --- /dev/null +++ b/bt2x-pi-pico/.vscode/launch.json @@ -0,0 +1,45 @@ +// The format of this file is specified in https://probe.rs/docs/tools/vscode/#start-a-debug-session-with-minimum-configuration +{ + "version": "0.2.0", + "configurations": [ + { + "preLaunchTask": "rust: cargo build", + "type": "probe-rs-debug", + "request": "launch", + "name": "rp2040-project", + "cwd": "${workspaceFolder}", + "chip": "rp2040", + // RP2040 doesn't support connectUnderReset + "connectUnderReset": false, + "speed": 4000, + "runtimeExecutable": "probe-rs-debugger", + "runtimeArgs": [ + "debug" + ], + "flashingConfig": { + "flashingEnabled": true, + "resetAfterFlashing": true, + "haltAfterReset": true, + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "target/thumbv6m-none-eabi/debug/rp2040-project-template", + "chip": "RP2040", + // Uncomment this if you've downloaded the SVD from + // https://github.com/raspberrypi/pico-sdk/raw/1.3.1/src/rp2040/hardware_regs/rp2040.svd + // and placed it in the .vscode directory + // "svdFile": "./.vscode/rp2040.svd", + "rttEnabled": true, + "options": { + "env": { + "DEFMT_LOG": "debug" + } + }, + } + ], + "consoleLogLevel": "Info", //Error, Warn, Info, Debug, Trace + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/bt2x-pi-pico/.vscode/settings.json b/bt2x-pi-pico/.vscode/settings.json new file mode 100644 index 0000000..5234c64 --- /dev/null +++ b/bt2x-pi-pico/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.cargo.target": "thumbv6m-none-eabi", + "rust-analyzer.checkOnSave.allTargets": false, + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/bt2x-pi-pico/Cargo.toml b/bt2x-pi-pico/Cargo.toml new file mode 100644 index 0000000..899c7bd --- /dev/null +++ b/bt2x-pi-pico/Cargo.toml @@ -0,0 +1,83 @@ +[package] +edition = "2021" +name = "rp2040-project-template" +version = "0.1.0" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = { version = "0.2.5", features = ["unproven"] } + +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +# We're using a Pico by default on this template +rp-pico = "0.7" + +embedded-alloc = "0.5.0" + +bt2x-embedded = { path = "../bt2x/bt2x-embedded" } +serde = { version = "1.0", default-features = false, features = ["derive"] } +# serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +serde-json-core = { version = "0.5.0" } + +# but you can use any BSP. Uncomment this to use the pro_micro_rp2040 BSP instead +# sparkfun-pro-micro-rp2040 = "0.3" + +# If you're not going to use a Board Support Package you'll need these: +# rp2040-hal = { version="0.8", features=["rt"] } +# rp2040-boot2 = "0.2" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true +incremental = false +opt-level = 3 +overflow-checks = true + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 3 +overflow-checks = false +strip = true + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true +incremental = false +opt-level = 3 +overflow-checks = true + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 3 diff --git a/bt2x-pi-pico/Embed.toml b/bt2x-pi-pico/Embed.toml new file mode 100644 index 0000000..9c91b13 --- /dev/null +++ b/bt2x-pi-pico/Embed.toml @@ -0,0 +1,39 @@ +[default.probe] +protocol = "Swd" +speed = 20000 +# If you only have one probe cargo embed will pick automatically +# Otherwise: add your probe's VID/PID/serial to filter + +## rust-dap +# usb_vid = "6666" +# usb_pid = "4444" +# serial = "test" + + +[default.flashing] +enabled = true + +[default.reset] +enabled = true +halt_afterwards = false + +[default.general] +chip = "RP2040" +log_level = "WARN" +# RP2040 does not support connect_under_reset +connect_under_reset = false + +[default.rtt] +enabled = true +up_mode = "NoBlockSkip" +channels = [ + { up = 0, down = 0, name = "name", up_mode = "NoBlockSkip", format = "Defmt" }, +] +timeout = 3000 +show_timestamps = true +log_enabled = false +log_path = "./logs" + +[default.gdb] +enabled = false +gdb_connection_string = "127.0.0.1:2345" diff --git a/bt2x-pi-pico/README.md b/bt2x-pi-pico/README.md new file mode 100644 index 0000000..d774c93 --- /dev/null +++ b/bt2x-pi-pico/README.md @@ -0,0 +1,267 @@ +# Project template for rp2040-hal + +This template is intended as a starting point for developing your own firmware based on the rp2040-hal. + +It includes all of the `knurling-rs` tooling as showcased in https://github.com/knurling-rs/app-template (`defmt`, `defmt-rtt`, `panic-probe`, `flip-link`) to make development as easy as possible. + +`probe-run` is configured as the default runner, so you can start your program as easy as +```sh +cargo run --release +``` + +If you aren't using a debugger (or want to use cargo-embed/probe-rs-debugger), check out [alternative runners](#alternative-runners) for other options + + +

+ +

Table of Contents

+
    +
  1. Requirements
  2. +
  3. Installation of development dependencies
  4. +
  5. Running
  6. +
  7. Alternative runners
  8. +
  9. Roadmap
  10. +
  11. Contributing
  12. +
  13. Code of conduct
  14. +
  15. License
  16. +
  17. Contact
  18. +
+
+ + +
+

Requirements

+ +- The standard Rust tooling (cargo, rustup) which you can install from https://rustup.rs/ + +- Toolchain support for the cortex-m0+ processors in the rp2040 (thumbv6m-none-eabi) + +- flip-link - this allows you to detect stack-overflows on the first core, which is the only supported target for now. + +- probe-run. Upstream support for RP2040 was added with version 0.3.1. + +- A CMSIS-DAP probe. (J-Link and other probes will not work with probe-run) + + You can use a second + [Pico as a CMSIS-DAP debug probe](debug_probes.md#raspberry-pi-pico). Details + on other supported debug probes can be found in + [debug_probes.md](debug_probes.md) + +
+ + +
+

Installation of development dependencies

+ +```sh +rustup target install thumbv6m-none-eabi +cargo install flip-link +# This is our suggested default 'runner' +cargo install probe-run +# If you want to use elf2uf2-rs instead of probe-run, instead do... +cargo install elf2uf2-rs --locked +``` + +
+ + + +
+

Running

+ +For a debug build +```sh +cargo run +``` +For a release build +```sh +cargo run --release +``` + +If you do not specify a DEFMT_LOG level, it will be set to `debug`. +That means `println!("")`, `info!("")` and `debug!("")` statements will be printed. +If you wish to override this, you can change it in `.cargo/config.toml` +```toml +[env] +DEFMT_LOG = "off" +``` +You can also set this inline (on Linux/MacOS) +```sh +DEFMT_LOG=trace cargo run +``` + +or set the _environment variable_ so that it applies to every `cargo run` call that follows: +#### Linux/MacOS/unix +```sh +export DEFMT_LOG=trace +``` + +Setting the DEFMT_LOG level for the current session +for bash +```sh +export DEFMT_LOG=trace +``` + +#### Windows +Windows users can only override DEFMT_LOG through `config.toml` +or by setting the environment variable as a separate step before calling `cargo run` +- cmd +```cmd +set DEFMT_LOG=trace +``` +- powershell +```ps1 +$Env:DEFMT_LOG = trace +``` + +```cmd +cargo run +``` + +
+ +
+

Alternative runners

+ +If you don't have a debug probe or if you want to do interactive debugging you can set up an alternative runner for cargo. + +Some of the options for your `runner` are listed below: + +* **cargo embed** + *Step 1* - Install [`cargo embed`](https://github.com/probe-rs/probe-rs/blob/master/cargo-embed): + + ```console + $ cargo install cargo-embed + ``` + + *Step 2* - Make sure your .cargo/config contains the following + + ```toml + [target.thumbv6m-none-eabi] + runner = "cargo embed" + ``` + + *Step 3* - Update settings in [Embed.toml](./Embed.toml) + - The defaults are to flash, reset, and start a defmt logging session + You can find all the settings and their meanings [in the cargo-embed repo](https://github.com/probe-rs/probe-rs/blob/master/cargo-embed/src/config/default.toml) + + *Step 4* - Use `cargo run`, which will compile the code and start the + specified 'runner'. As the 'runner' is cargo embed, it will flash the device + and start running immediately + + ```console + $ cargo run --release + ``` + +* **probe-rs-debugger** + + *Step 1* - Download [`probe-rs-debugger VSCode plugin 0.4.0`](https://github.com/probe-rs/vscode/releases/download/v0.4.0/probe-rs-debugger-0.4.0.vsix) + + *Step 2* - Install `probe-rs-debugger VSCode plugin` + ```console + $ code --install-extension probe-rs-debugger-0.4.0.vsix + ``` + + *Step 3* - Install `probe-rs-debugger` + ```console + $ cargo install probe-rs-debugger + ``` + + *Step 4* - Open this project in VSCode + + *Step 5* - Launch a debug session by choosing `Run`>`Start Debugging` (or press F5) + +* **Loading a UF2 over USB** + *Step 1* - Install [`elf2uf2-rs`](https://github.com/JoNil/elf2uf2-rs): + + ```console + $ cargo install elf2uf2-rs --locked + ``` + + *Step 2* - Make sure your .cargo/config contains the following + + ```toml + [target.thumbv6m-none-eabi] + runner = "elf2uf2-rs -d" + ``` + + The `thumbv6m-none-eabi` target may be replaced by the all-Arm wildcard + `'cfg(all(target_arch = "arm", target_os = "none"))'`. + + *Step 3* - Boot your RP2040 into "USB Bootloader mode", typically by rebooting + whilst holding some kind of "Boot Select" button. On Linux, you will also need + to 'mount' the device, like you would a USB Thumb Drive. + + *Step 4* - Use `cargo run`, which will compile the code and start the + specified 'runner'. As the 'runner' is the elf2uf2-rs tool, it will build a UF2 + file and copy it to your RP2040. + + ```console + $ cargo run --release + ``` + +* **Loading with picotool** + As ELF files produced by compiling Rust code are completely compatible with ELF + files produced by compiling C or C++ code, you can also use the Raspberry Pi + tool [picotool](https://github.com/raspberrypi/picotool). The only thing to be + aware of is that picotool expects your ELF files to have a `.elf` extension, and + by default Rust does not give the ELF files any extension. You can fix this by + simply renaming the file. + + This means you can't easily use it as a cargo runner - yet. + + Also of note is that the special + [pico-sdk](https://github.com/raspberrypi/pico-sdk) macros which hide + information in the ELF file in a way that `picotool info` can read it out, are + not supported in Rust. An alternative is TBC. + +
+ + + +## Roadmap + +NOTE These packages are under active development. As such, it is likely to +remain volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp2040-project-template/issues) for a list of +proposed features (and known issues). + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +The steps are: + +1. Fork the Project by clicking the 'Fork' button at the top of the page. +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Make some changes to the code or documentation. +4. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +5. Push to the Feature Branch (`git push origin feature/AmazingFeature`) +6. Create a [New Pull Request](https://github.com/rp-rs/rp-hal/pulls) +7. An admin will review the Pull Request and discuss any changes that may be required. +8. Once everyone is happy, the Pull Request can be merged by an admin, and your work is part of our project! + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], and the maintainer of this crate, the [rp-rs team], promises +to intervene to uphold that code of conduct. + +[CoC]: CODE_OF_CONDUCT.md +[rp-rs team]: https://github.com/orgs/rp-rs/teams/rp-rs + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache +2.0_ License. That means you can chose either the MIT licence or the +Apache-2.0 licence when you re-use this code. See `MIT` or `APACHE2.0` for more +information on each specific licence. + +Any submissions to this project (e.g. as Pull Requests) must be made available +under these terms. + +## Contact + +Raise an issue: [https://github.com/rp-rs/rp2040-project-template/issues](https://github.com/rp-rs/rp2040-project-template/issues) +Chat to us on Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) diff --git a/bt2x-pi-pico/build.rs b/bt2x-pi-pico/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/bt2x-pi-pico/build.rs @@ -0,0 +1,31 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/bt2x-pi-pico/debug_probes.md b/bt2x-pi-pico/debug_probes.md new file mode 100644 index 0000000..830446f --- /dev/null +++ b/bt2x-pi-pico/debug_probes.md @@ -0,0 +1,47 @@ +# Compatible CMSIS-DAP debug probes + +## Raspberry Pi Pico + +You can use a second Pico as your debugger. + +Download one of these firmware files: + +- [picoprobe.uf2](https://github.com/raspberrypi/picoprobe/releases/download/picoprobe-cmsis-v1.0.1/picoprobe.uf2) - + Official raspberrypi probe firmware supporting CMSIS-DAP. +- [raspberry_pi_pico-DapperMime.uf2](https://github.com/majbthrd/DapperMime/releases/download/20210225/raspberry_pi_pico-DapperMime.uf2) - + Based upon an older version of the CMSIS-DAP sources. +- [rust-dap-pico-ramexec-setclock.uf2](https://raw.githubusercontent.com/9names/binary-bits/main/rust-dap-pico-ramexec-setclock.uf2) - + If you have good wiring between your Pico's, this firmware will give faster + programming. + +Then: + +1. Put the Pico into USB Mass Storage Mode by holding the BOOTSEL button while connecting it to your computer with a USB cable +2. Open the drive RPI-RP2 when prompted +3. Copy the uf2 firmware file from Downloads into RPI-RP2 +4. Connect the debug pins of your CMSIS-DAP Pico to the target one + - Connect GP2 on the Probe to SWCLK on the Target + - Connect GP3 on the Probe to SWDIO on the Target + - Connect a ground line from the CMSIS-DAP Probe to the Target too + +## WeAct MiniF4 +https://therealprof.github.io/blog/usb-c-pill-part1/ + +## HS-Probe +https://github.com/probe-rs/hs-probe + +## ST-LINK v2 clone +It's getting harder to source these with stm32f103's as time goes on, so you might be better off choosing a stm32f103 dev board + +Firmware: https://github.com/devanlai/dap42 + +## LPC-Link2 +https://www.nxp.com/design/microcontrollers-developer-resources/lpc-link2:OM13054 + +## MCU-Link +https://www.nxp.com/part/MCU-LINK#/ + +## DAPLink +You can use DAPLink firmware with any of it's supported chips (LPC4322, LPC11U35, K20, K22, KL26). You'll need to use the 'develop' branch to use GCC to build it. You'll need to find a chip with the correct + +Firmware source: https://github.com/ARMmbed/DAPLink/tree/develop diff --git a/bt2x-pi-pico/memory.x b/bt2x-pi-pico/memory.x new file mode 100644 index 0000000..070eac7 --- /dev/null +++ b/bt2x-pi-pico/memory.x @@ -0,0 +1,15 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; \ No newline at end of file diff --git a/bt2x-pi-pico/rust-toolchain.toml b/bt2x-pi-pico/rust-toolchain.toml new file mode 100644 index 0000000..e437953 --- /dev/null +++ b/bt2x-pi-pico/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +targets = [ "thumbv6m-none-eabi" ] diff --git a/bt2x-pi-pico/src/main.rs b/bt2x-pi-pico/src/main.rs new file mode 100644 index 0000000..fa14df5 --- /dev/null +++ b/bt2x-pi-pico/src/main.rs @@ -0,0 +1,101 @@ +//! Blinks the LED on a Pico board +//! +//! This will blink an LED attached to GP25, which is the pin the Pico uses for the on-board LED. +#![no_std] +#![no_main] + +use embedded_alloc::Heap; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +use bsp::entry; +use defmt::*; +use defmt_rtt as _; +use embedded_hal::digital::v2::OutputPin; +use panic_probe as _; + +// Provide an alias for our BSP so we can switch targets quickly. +// Uncomment the BSP you included in Cargo.toml, the rest of the code does not need to change. +use rp_pico as bsp; +// use sparkfun_pro_micro_rp2040 as bsp; + +use bsp::hal::{ + clocks::{init_clocks_and_plls, Clock}, + pac, + sio::Sio, + watchdog::Watchdog, +}; +use bt2x_embedded::verify_bt; + +#[entry] +fn main() -> ! { + info!("Program start"); + + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + let sio = Sio::new(pac.SIO); + + // External high-speed crystal on the pico board is 12Mhz + let external_xtal_freq_hz = 12_000_000u32; + let clocks = init_clocks_and_plls( + external_xtal_freq_hz, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + let pins = bsp::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // This is the correct pin on the Raspberry Pico board. On other boards, even if they have an + // on-board LED, it might need to be changed. + // Notably, on the Pico W, the LED is not connected to any of the RP2040 GPIOs but to the cyw43 module instead. If you have + // a Pico W and want to toggle a LED with a simple GPIO output pin, you can connect an external + // LED to one of the GPIO pins, and reference that pin here. + let mut led_pin = pins.led.into_push_pull_output(); + info!("on!"); + led_pin.set_high().unwrap(); + let result = verify_bt( + b"../../example/rekor.pem", + b"../../example/fulcio.pem", + b"../../example/hello-world.bundle.json", + b"../../example/hello-world.wasm", + &[], + ); + + led_pin.set_low().unwrap(); + let bundle = if let Ok(bundle) = result { + bundle + } else { + loop { + info!("on!"); + led_pin.set_high().unwrap(); + delay.delay_ms(500); + info!("off!"); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } + }; + for i in 0..(10) { + info!("on!"); + led_pin.set_high().unwrap(); + delay.delay_ms(1000 / 30); + info!("off!"); + led_pin.set_low().unwrap(); + delay.delay_ms(1000 / 30); + } + loop {} +} diff --git a/bt2x/.dockerignore b/bt2x/.dockerignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/bt2x/.dockerignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/bt2x/.gitignore b/bt2x/.gitignore new file mode 100644 index 0000000..fb3dec9 --- /dev/null +++ b/bt2x/.gitignore @@ -0,0 +1,19 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +/.out/pi-pico-elf2 +/.out/pi-pico-elf2.canonical.json +/.out/pi-pico-elf2.json diff --git a/bt2x/Cargo.toml b/bt2x/Cargo.toml new file mode 100644 index 0000000..d1a4466 --- /dev/null +++ b/bt2x/Cargo.toml @@ -0,0 +1,76 @@ +[workspace] +resolver = "2" +members = [ + "bt2x-common", + "bt2x-server", + "bt2x-embedded", + "bt2x-cli", + "bt2x-flasher", + "bt2x-monitor", + "bt2x-ota", + "bt2x-ota-common", +] + +[workspace.dependencies] +anyhow = "1.0" +async-trait = "0.1" +axum = "0.7.5" +base64 = { version = "0.22.1", default-features = false } +base64ct = { version = "1.6.0", default-features = false } +bitfield = { version = "0.15.0", default-features = false } +cargo-audit = "0.20.0" +clap = "4.2" +crossbeam = "0.8.2" +cyclonedx-bom = "0.5.0" +defmt = { version = "0.3.5", default-features = false } +der = { version = "0.7", default-features = false } +dhat = "0.3.2" +digest = "0.10.6" +ecdsa = { version = "0.16.1", default-features = false } +embedded-io-async = { version = "0.6", default-features = false } +futures = "0.3.30" +hex = { version = "0.4.3", default-features = false } +hex-literal = "0.4.1" +humantime = "2.1.0" +itertools = "0.12.1" +oci-distribution = { version = "0.10.0", default-features = false } +olpc-cjson = "0.1" +p256 = { version = "0.13.0", default-features = false } +pem-rfc7468 = { version = "0.7.0", default-features = false } +pipe = "0.4.0" +pkcs8 = { version = "0.10.1", default-features = false } +postcard = "1.0" +probe-rs = "0.23.0" +reqwest = { version = "0.12.4", features = ["json", "rustls-tls"] } +rustsec = "0.29.2" +sct = "0.7.0" +serde = { version = "1.0", default-features = false } +serde-json-core = "0.5.0" +serde_json = { version = "1.0", default-features = false } +serde_with = "3.8.1" +serde_yaml = "0.9" +sha2 = { version = "0.10.6", default-features = false } +signature = "2.0.0" +sigstore = { git = "https://github.com/vembacher/sigstore-rs", rev = "58d3b5f1285496ef2709e50dd6e75d507700d534" } +spki = { version = "0.7.0", default-features = false } +thiserror = "1.0" +tokio = "1" +tough = "0.17" +tracing = "0.1" +tracing-core = "0.1" +tracing-subscriber = "0.3" +typed-builder = "0.18.2" +url = "2.3.1" +uuid = "1.3.3" +x509-cert = { version = "0.2.1", default-features = false } +log = "0.4" +testcontainers = "0.15" +testcontainers-modules = "0.3" +tempfile = "3.10.1" +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +embedded-storage = "0.3" +path-clean = "1.0" +chrono = { version = "0.4" } +rustls-pki-types = "1.8.0" +rustls-pemfile = "2.1.3" \ No newline at end of file diff --git a/bt2x/LICENSE.txt b/bt2x/LICENSE.txt new file mode 100644 index 0000000..4052ad4 --- /dev/null +++ b/bt2x/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bt2x/README.md b/bt2x/README.md new file mode 100644 index 0000000..c4590df --- /dev/null +++ b/bt2x/README.md @@ -0,0 +1,13 @@ +# BT²X + +## Building the Docs + +To view the documentation within the code (RustDocs) run the following command: + +```sh +# with dependencies +cargo doc --open + +## without dependencies (faster) +cargo doc --open --no-deps +``` \ No newline at end of file diff --git a/bt2x/bt2x-cli/Cargo.toml b/bt2x/bt2x-cli/Cargo.toml new file mode 100644 index 0000000..e660953 --- /dev/null +++ b/bt2x/bt2x-cli/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bt2x-cli" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { workspace = true, features = ["derive"] } +oci-distribution = { workspace = true, features = ["rustls-tls"] } +url = { workspace = true } +tracing = { workspace = true } +tracing-core = { workspace = true } +tracing-subscriber = { workspace = true, features = [ + "fmt", + "std", + "env-filter", +] } +tokio = { workspace = true, features = ["rt-multi-thread"] } +sigstore = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +olpc-cjson = { workspace = true } +sha2 = { workspace = true, features = ["oid"] } +base64 = { workspace = true } +hex = { workspace = true } +bt2x-common = { path = "../bt2x-common" } +itertools = { workspace = true } diff --git a/bt2x/bt2x-cli/src/main.rs b/bt2x/bt2x-cli/src/main.rs new file mode 100644 index 0000000..489ecb2 --- /dev/null +++ b/bt2x/bt2x-cli/src/main.rs @@ -0,0 +1,357 @@ +use clap::Parser; +use itertools::Itertools; +use sigstore::registry::OciReference; +use std::env::set_current_dir; +use std::ops::Add; +use std::path::PathBuf; +use std::process::Command; +use std::str::FromStr; +use tracing::{debug, info}; +use tracing_subscriber::filter::Targets; +use tracing_subscriber::prelude::*; + +#[derive(Debug, Parser)] +#[clap(author, version /*, trailing_var_arg = true*/)] +struct Args { + /// tagged OCI reference to which the binary will be published + #[arg(long, value_name = "OCI IMAGE")] + tag: OciReference, + /// Rust binary target that will get published + #[arg(long, value_name = "BINARY")] + bin: Vec, + #[arg(long, value_name = "PACKAGE")] + package: Vec, + /// LLVM target (e.g. `thumbv6m-none-eabi` for a Pi Pico). + #[arg(long, value_name = "LLVM TARGET")] + target: Option, + #[arg(long)] + fulcio_url: Option, + #[arg(long)] + fulcio_public_key: Option, + #[arg(long)] + rekor_url: Option, + #[arg(long)] + rekor_public_key: Option, + #[arg(long)] + dir: Option, + #[arg(long)] + ct_log_url: Option, + #[arg(long)] + ct_log_public_key: Option, + #[arg(long)] + oidc_issuer: Option, + /// Allow HTTP to be used. + #[clap(long)] + http: bool, + /// disable compiling with `--release flag` + #[clap(long)] + debug: bool, + /// log level of this application, defaults to Info. + #[clap(long, value_enum, default_value_t = LogLevel::Info)] + log_level: LogLevel, +} + +#[tokio::main] +async fn main() { + let args = Args::parse(); + configure_logging(&args); + let mut cargo_args = vec![]; + let mut objcopy_args = vec![]; + args.bin + .iter() + .for_each(|b| cargo_args.extend(["--bin".to_string(), b.clone()])); + args.bin + .iter() + .for_each(|b| objcopy_args.extend(["--bin".to_string(), b.clone()])); + args.package + .iter() + .for_each(|b| cargo_args.extend(["--package".to_string(), b.clone()])); + if !args.debug { + cargo_args.push("--release".to_string()); + objcopy_args.push("--release".to_string()); + } + if let Some(dir) = args.dir { + set_current_dir(dir).expect("failed to change directory"); + } + if let Some(target) = &args.target { + cargo_args.extend(["--target".to_string(), target.clone()]); + objcopy_args.extend([ + "--".to_string(), + "-O".to_string(), + "binary".to_string(), + format!("target/{target}/release/{}.bin", &args.bin[0]), + ]); + } + info!("Starting `cargo build {}`", cargo_args.iter().join(" ")); + + let output = Command::new("cargo") + .arg("build") + .args(cargo_args) + //.args(args.command) + .output() + .expect("Cargo failed"); + let stdout = std::str::from_utf8(output.stdout.as_slice()).unwrap_or("OUTPUT WAS NOT UTF-8"); + let stderr = std::str::from_utf8(output.stderr.as_slice()).unwrap_or("OUTPUT WAS NOT UTF-8"); + info!("Cargo finished."); + info!("Cargo output:"); + info!("------------------------STDOUT------------------------\n{stdout}"); + info!("------------------------STDERR------------------------\n{stderr}"); + info!("------------------------------------------------------"); + + info!("Starting `cargo objcopy {}`", objcopy_args.iter().join(" ")); + let output = Command::new("cargo") + .arg("objcopy") + .args(objcopy_args) + //.args(args.command) + .output() + .expect("Cargo failed"); + let stdout = std::str::from_utf8(output.stdout.as_slice()).unwrap_or("OUTPUT WAS NOT UTF-8"); + let stderr = std::str::from_utf8(output.stderr.as_slice()).unwrap_or("OUTPUT WAS NOT UTF-8"); + info!("Cargo finished."); + info!("Cargo output:"); + info!("------------------------STDOUT------------------------\n{stdout}"); + info!("------------------------STDERR------------------------\n{stderr}"); + info!("------------------------------------------------------"); + debug!("reading input binary"); + let bin_path = if let Some(target) = &args.target { + PathBuf::from("./target").join(target.as_str()) + } else { + PathBuf::from("./target") + } + .join(if args.debug { "debug" } else { "release" }) + .join(format!("{}.bin", args.bin[0].as_str())); + + let ct_log_public_key_file = args.ct_log_public_key.unwrap(); + let root_file = args.fulcio_public_key.unwrap(); + let rekor_public_key = args.rekor_public_key.unwrap(); + let ct_log_public_key_file = ct_log_public_key_file.to_str().unwrap(); + let root_file = root_file.to_str().unwrap(); + let rekor_public_key = rekor_public_key.to_str().unwrap(); + + let mut command = Command::new("cosign"); + command + .args(["upload", "blob"]) + .args(["-f", bin_path.to_str().unwrap(), &args.tag.whole()]); + info!("running {command:?}"); + let output_tag = command + .output() + .map_err(|err| err.to_string()) + .map(|output| { + std::str::from_utf8(&output.stdout) + .map_err(|err| err.to_string()) + .and_then(|s| { + OciReference::from_str(s.trim_end_matches('\n')).map_err(|err| err.to_string()) + }) + }) + .and_then(|reference| reference) + .expect("could not get output tag from cosign output"); + + let oidc_issuer = args.oidc_issuer.unwrap().to_string(); + let rekor_url = args.rekor_url.unwrap().to_string(); + let fulcio_url = args.fulcio_url.unwrap().to_string(); + + info!("running cosign sign-blob "); + + let mut command = Command::new("cosign"); + command + .args(["sign-blob", bin_path.to_str().unwrap()]) + .args(["--oidc-issuer", oidc_issuer.as_str()]) + .args(["--rekor-url", rekor_url.as_str()]) + .args(["--fulcio-url", fulcio_url.as_str()]) + .args(["--bundle", "bundle.json"]) + .env("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", ct_log_public_key_file) + .env("SIGSTORE_ROOT_FILE", root_file) + .env("SIGSTORE_REKOR_PUBLIC_KEY", rekor_public_key) + .env("COSIGN_EXPERIMENTAL", "1"); + debug!("Running {command:?}"); + command + .spawn() + .expect("`cosign sign-blob` failed") + .wait() + .expect("could not wait on cosign child process") + .code() + .filter(|&c| c == 0) + .expect("`cosign sign-blob` failed"); + + let bundle_tag = OciReference::with_tag( + output_tag.registry().to_string(), + output_tag.repository().to_string(), + output_tag + .digest() + .unwrap() + .replace(':', "-") + .add(".bundle"), + ); + info!("uploading bundle json as blob to {bundle_tag:?}"); + let mut command = Command::new("cosign"); + command + .args(["upload", "blob"]) + .args(["-f", "bundle.json", &bundle_tag.whole()]); + debug!("Running {command:?}"); + command + .spawn() + .expect("`cosign upload blob` failed") + .wait() + .expect("could not wait on cosign child process") + .code() + .filter(|&c| c == 0) + .expect("`cosign upload blob` failed"); + info!("Completed!"); + + info!("running cosign sign "); + let mut command = Command::new("cosign"); + command + .args(["sign", &output_tag.whole()]) + .args(["--oidc-issuer", oidc_issuer.as_str()]) + .args(["--rekor-url", rekor_url.as_str()]) + .args(["--fulcio-url", fulcio_url.as_str()]) + .env("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", ct_log_public_key_file) + .env("SIGSTORE_ROOT_FILE", root_file) + .env("SIGSTORE_REKOR_PUBLIC_KEY", rekor_public_key) + .env("COSIGN_EXPERIMENTAL", "1"); + debug!("Running {command:?}"); + command + .spawn() + .expect("`cosign sign-blob` failed") + .wait() + .expect("could not wait on cosign child process") + .code() + .filter(|&c| c == 0) + .expect("`cosign sign-blob` failed"); + + // let data = fs::read(bin_path).expect("failed to read input"); + // + // let mut oci_client = oci_distribution::client::Client::new(ClientConfig { + // protocol: if args.http { Http } else { Https }, + // ..Default::default() + // }); + // let image = oci_distribution::Reference::from_str(&args.tag.whole()).expect("this should never fail"); + // let layers = [ImageLayer { + // data: data.clone(), + // media_type: "application/octet-stream".to_string(), + // annotations: None, + // }]; + // let config = oci_distribution::client::Config { + // data: r#"{}"# + // .as_bytes().to_vec(), + // media_type: "application/vnd.oci.image.config.v1+json".to_string(), + // annotations: None, + // }; + // let mut image_manifest = manifest::OciImageManifest::build(&layers, &config, None); + // image_manifest.media_type = Some("application/vnd.oci.image.manifest.v1+json".to_string()); + // debug!("Manifest: {image_manifest:#?}"); + // + // debug!("uploading to OCI registry..."); + // let res = match oci_client.push( + // &image, + // &layers, + // config, + // &Anonymous, + // Some(image_manifest), + // ).await { + // Ok(res) => { res } + // Err(err) => { + // error!("failed to push {err:?}"); + // exit(-1); + // } + // }; + // debug!("Upload completed."); + // + // debug!("Creating Fulcio client."); + // let fulcio_client = sigstore::fulcio::FulcioClient::new( + // args.fulcio_url.unwrap(), + // Oauth(sigstore::fulcio::oauth::OauthTokenProvider::default() + // .with_issuer("http://dex-idp:8888/")), + // ); + // debug!("Sending certificate request."); + // let (signer, cert) = match fulcio_client.request_cert(SigningScheme::ECDSA_P256_SHA256_ASN1).await { + // Ok((SigStoreSigner::ECDSA_P256_SHA256_ASN1(signer), cert)) => { (signer, cert) } + // Err(err) => { + // error!("requesting fulcio cert failed {err}"); + // exit(-1); + // } + // Ok((signer, cert)) => { + // error!("unexpected response {signer:?}"); + // exit(-1); + // } + // }; + // let sigining_cert = Base64Standard.encode(cert.to_string()); + // let signature = signer.sign(data.as_slice()) + // .expect("failed to create signature"); + // + // let digest = Sha256::digest(data.as_slice()); + // let hex_digest = hex::encode(digest.as_slice()); + // + // + // let proposed_entry = ProposedEntry::Hashedrekord { + // api_version: "0.0.1".to_string(), + // spec: Spec { + // signature: Signature { + // content: Base64Standard.encode(signature.as_slice()), + // public_key: PublicKey::new(sigining_cert), + // }, + // data: Data { + // hash: Hash { algorithm: AlgorithmKind::sha256, value: hex_digest }, + // }, + // }, + // }; + // + // let entry = create_log_entry( + // &RekorConfig { + // base_path: args.rekor_url.expect("please add rekor url").to_string(), + // user_agent: None, + // client: Default::default(), + // basic_auth: None, + // oauth_access_token: None, + // bearer_access_token: None, + // api_key: None, + // }, + // proposed_entry, + // ).await.expect("failed to create entry"); + // let cosign_bundle = CosignBundle::from_signing_material_and_log_entry( + // signature.as_slice(), + // cert.to_string().as_bytes(), + // &entry, + // ).expect("failed to produce cosign bundle"); + // let bundle_json = serde_json::to_string(&cosign_bundle).expect("failed to produce bundle JSON"); + // write("bundle-cli.json", bundle_json.as_bytes()).expect("failed to write"); + // let compact_bundle = CompactRekorBundle::from(cosign_bundle); + // let bundle_json = serde_json::to_string(&compact_bundle).expect("failed to produce bundle JSON"); + // write("bundle-cli-compact.json", bundle_json.as_bytes()).expect("failed to write"); + // debug!("{entry:?}"); +} + +#[derive(clap::ValueEnum, Clone, Debug)] +enum LogLevel { + Debug, + Info, + Warn, + Error, + Trace, +} + +impl From<&LogLevel> for tracing_core::LevelFilter { + fn from(value: &LogLevel) -> Self { + match value { + LogLevel::Debug => tracing_core::Level::DEBUG.into(), + LogLevel::Info => tracing_core::Level::INFO.into(), + LogLevel::Warn => tracing_core::Level::WARN.into(), + LogLevel::Error => tracing_core::Level::ERROR.into(), + LogLevel::Trace => tracing_core::Level::TRACE.into(), + } + } +} + +fn configure_logging(args: &Args) { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with( + Targets::new() + .with_target(env!("CARGO_PKG_NAME").replace('-', "_"), &args.log_level) + .with_target("sigstore", tracing_core::Level::INFO) + .with_target("oci_distribution", tracing_core::Level::DEBUG) + .with_target("hyper", tracing_core::Level::INFO) + .with_target("reqwest", tracing_core::Level::INFO), + ) + .init(); +} diff --git a/bt2x/bt2x-common/Cargo.toml b/bt2x/bt2x-common/Cargo.toml new file mode 100644 index 0000000..f9f2a0b --- /dev/null +++ b/bt2x/bt2x-common/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "bt2x-common" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[features] +default = ["bt", "oci"] +oci = ["dep:oci-distribution"] +# sbom = ["dep:cyclonedx-bom", "dep:rustsec", "dep:cargo-audit"] +bt = ["dep:sigstore", "dep:url", "dep:oci-distribution"] + +[dependencies] +# bt +sigstore = { workspace = true, optional = true } +sha2 = { workspace = true, features = ["oid"] } +# oci +oci-distribution = { workspace = true, features = [ + "rustls-tls", +], optional = true } +# sbom/vulnerability scanning related +cyclonedx-bom = { workspace = true, optional = true } +rustsec = { workspace = true, optional = true } +cargo-audit = { workspace = true, optional = true } + +# misc +url = { workspace = true, optional = true } +tracing = { workspace = true } +async-trait = { workspace = true } +typed-builder = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_with = { workspace = true, features = ["base64", "hex", "json"] } +serde_json = { workspace = true } +base64 = { workspace = true } +hex = { workspace = true } +olpc-cjson = { workspace = true } +x509-cert = { workspace = true, features = ["pem"] } +spki = { workspace = true, features = ["fingerprint"] } +p256 = { workspace = true, features = ["pem", "ecdsa"] } +anyhow = { workspace = true } +digest = { workspace = true } +itertools = { workspace = true } +thiserror = { workspace = true } +reqwest = { workspace = true } +sct = { workspace = true } +tough = { workspace = true } +futures = { workspace = true } +tuf-no-std = { path = "../../tuf-no-std/tuf-no-std", default-features = false, features = [ + "der", + "verify", + "ecdsa", + "async", +] } +chrono = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true } +hex-literal = { workspace = true } +serde_yaml = { workspace = true } diff --git a/bt2x/bt2x-common/src/artifact/mod.rs b/bt2x/bt2x-common/src/artifact/mod.rs new file mode 100644 index 0000000..1779b0b --- /dev/null +++ b/bt2x/bt2x-common/src/artifact/mod.rs @@ -0,0 +1 @@ +pub mod source; diff --git a/bt2x/bt2x-common/src/artifact/source/bundle.rs b/bt2x/bt2x-common/src/artifact/source/bundle.rs new file mode 100644 index 0000000..767fe07 --- /dev/null +++ b/bt2x/bt2x-common/src/artifact/source/bundle.rs @@ -0,0 +1,54 @@ +use crate::artifact::source::ArtifactSource; +use crate::error::Bt2XError; +use async_trait::async_trait; +use sigstore::cosign::bundle::SignedArtifactBundle; +use sigstore::crypto::CosignVerificationKey; +use std::fs::read; +use url::Url; + +pub struct BundleSource {} + +#[async_trait(? Send)] +impl ArtifactSource, url::Url> for BundleSource { + async fn get_artifact(&mut self, identifier: &Url) -> Result, Bt2XError> { + self.get_blob(identifier) + } + + #[allow(unused_variables)] + async fn get_bundle(&mut self, identifier: &Url) -> Result, Bt2XError> { + unimplemented!() + } +} + +impl BundleSource { + fn get_blob(&mut self, url: &Url) -> Result, Bt2XError> { + if url.scheme() == "file" { + let data = read(url.to_file_path().expect("failed get file path from url"))?; + return Ok(data); + } + unimplemented!("only supports files for now.") + } + pub fn get_bundle( + &mut self, + url: &Url, + rekor_pub_key: &CosignVerificationKey, + ) -> Result { + let data = if url.scheme() == "file" { + read(url.to_file_path().expect("failed get file path from url"))? + } else { + unimplemented!("only supports files for now"); + }; + let bundle = SignedArtifactBundle::new_verified( + String::from_utf8(data).unwrap().as_str(), + rekor_pub_key, + ) + .unwrap(); + + Ok(bundle) + } +} + +pub struct BlobBundle { + pub data: Vec, + pub bundle: SignedArtifactBundle, +} diff --git a/bt2x/bt2x-common/src/artifact/source/mod.rs b/bt2x/bt2x-common/src/artifact/source/mod.rs new file mode 100644 index 0000000..bac5893 --- /dev/null +++ b/bt2x/bt2x-common/src/artifact/source/mod.rs @@ -0,0 +1,15 @@ +use crate::error::Bt2XError; +use async_trait::async_trait; + +#[async_trait(? Send)] +pub trait ArtifactSource { + async fn get_artifact(&mut self, identifier: &Identifier) -> Result; + + async fn get_bundle(&mut self, identifier: &Identifier) -> Result; +} + +#[cfg(feature = "oci")] +pub mod oci; + +#[cfg(feature = "bt")] +pub mod bundle; diff --git a/bt2x/bt2x-common/src/artifact/source/oci.rs b/bt2x/bt2x-common/src/artifact/source/oci.rs new file mode 100644 index 0000000..2ec6f85 --- /dev/null +++ b/bt2x/bt2x-common/src/artifact/source/oci.rs @@ -0,0 +1,115 @@ +use crate::artifact::source::ArtifactSource; +use crate::error::Bt2XError; +use async_trait::async_trait; +use oci_distribution::client::{ClientConfig, ImageData}; +use oci_distribution::secrets::RegistryAuth; +use sigstore::registry::OciReference; +use std::ops::Add; +use std::str::FromStr; +use tracing::debug; + +pub struct OciSource { + pub client: oci_distribution::Client, + pub(crate) auth: RegistryAuth, +} + +impl OciSource { + pub fn new(config: ClientConfig, auth: RegistryAuth) -> Self { + OciSource { + auth, + client: oci_distribution::Client::new(config), + } + } + + async fn pull_image(&mut self, image: &OciReference) -> Result { + debug!("pulling image at {image:?}"); + let image = oci_distribution::Reference::from_str(&image.whole()).unwrap(); + self.client + .pull( + &image, + &self.auth, + vec![ + oci_distribution::manifest::WASM_LAYER_MEDIA_TYPE, + oci_distribution::manifest::WASM_CONFIG_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_MANIFEST_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_MANIFEST_LIST_MEDIA_TYPE, + oci_distribution::manifest::OCI_IMAGE_INDEX_MEDIA_TYPE, + oci_distribution::manifest::OCI_IMAGE_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_CONFIG_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_DOCKER_CONFIG_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_LAYER_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_LAYER_GZIP_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_DOCKER_LAYER_TAR_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_DOCKER_LAYER_GZIP_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_LAYER_NONDISTRIBUTABLE_MEDIA_TYPE, + oci_distribution::manifest::IMAGE_LAYER_NONDISTRIBUTABLE_GZIP_MEDIA_TYPE, + "text/plain", + "application/octet-stream", + ], + ) + .await + .map_err(|err| err.into()) + } + async fn pull_blob(&mut self, image: &OciReference) -> Result, Bt2XError> { + debug!("pulling blob at {image:?}"); + let digest = image + .digest() + .ok_or(Bt2XError::MissingDigestInOciReference(image.clone()))?; + let image = OciReference::with_digest( + image.registry().to_string(), + image.repository().to_string(), + digest.to_string(), + ); + self.pull_image(&image) + .await + .and_then(|mut image| { + image + .layers + .pop() + .ok_or(Bt2XError::MissingContainerImageLayers) + }) + .map(|layer| layer.data) + } + async fn pull_bundle(&mut self, image: &OciReference) -> Result, Bt2XError> { + debug!("pulling bundle at {image:?}"); + let digest = image + .digest() + .ok_or(Bt2XError::MissingDigestInOciReference(image.clone()))?; + let image = OciReference::with_tag( + image.registry().to_string(), + image.repository().to_string(), + digest.to_string().replace(':', "-").add(".bundle"), + ); + debug!("{image}"); + self.pull_image(&image) + .await + .and_then(|mut image| { + image + .layers + .pop() + .ok_or(Bt2XError::MissingContainerImageLayers) + }) + .map(|layer| layer.data) + } +} + +pub struct OciImage { + pub data: ImageData, +} + +#[async_trait(? Send)] +impl ArtifactSource, OciReference> for OciSource { + async fn get_artifact(&mut self, identifier: &OciReference) -> Result, Bt2XError> { + identifier + .digest() + .ok_or(Bt2XError::MissingDigestInOciReference(identifier.clone()))?; + self.pull_blob(identifier).await + } + + async fn get_bundle(&mut self, identifier: &OciReference) -> Result, Bt2XError> { + identifier + .digest() + .ok_or(Bt2XError::MissingDigestInOciReference(identifier.clone()))?; + self.pull_bundle(identifier).await + } +} diff --git a/bt2x/bt2x-common/src/error.rs b/bt2x/bt2x-common/src/error.rs new file mode 100644 index 0000000..ee44bc9 --- /dev/null +++ b/bt2x/bt2x-common/src/error.rs @@ -0,0 +1,55 @@ +use std::str::FromStr; + +use thiserror::Error; + +use crate::{gossip::Checkpoint, merkle::verify::ProofError}; + +#[derive(Error, Debug)] +pub enum Bt2XError { + #[error("failed to verify the SCT of a certificate {0:?}")] + SctValidationFailed(sct::Error), + #[error("certificate does not contain the required extensions for SCT validation")] + CertificateIsMissingCtExtensions, + #[error("failed to fetch a log info entry from the Rekor log: {0:?}")] + FailedToFetchLogInfo( + sigstore::rekor::apis::Error, + ), + #[error("could not parse the provided log checkpoint {0:?}")] + FailedToParseLogCheckpoint(::Err), + #[error("failed to verify the provided log checkpoint")] + CheckpointSignatureValidationFailed, + #[error("failed to fetch a log proof from the Rekor log: {0:?}")] + FailedToFetchLogProof( + sigstore::rekor::apis::Error, + ), + #[error("sending checkpoint failed {0:?}")] + GossipingCheckpointsFailed(reqwest::Error), + #[error("sending checkpoint failed {0:?}")] + DeserializingGossipResponseFailed(reqwest::Error), + #[error("error during I/O {0:?}")] + IoError(#[from] std::io::Error), + #[error("error while interacting with OCI registry {0:?}")] + OciError(#[from] oci_distribution::errors::OciDistributionError), + #[error("reference is required to have a digest {0:?}")] + MissingDigestInOciReference(sigstore::registry::OciReference), + #[error("container image is missing the required layers")] + MissingContainerImageLayers, + #[error("failure during TUF update {0:?}")] + TufError(tuf_no_std::TufError), + #[error("serialization to JSON failed {0:?}")] + JsonSerializationError(serde_json::Error), + #[error("deserialization from JSON failed {0:?}")] + JsonDeserializationError(serde_json::Error), + #[error("hex decode failed {0:?}")] + HexDecodingError(hex::FromHexError), + #[error("internal error {0:?}")] + InternalError(String), + #[error("hash length not equal to 32 bytes")] + InvalidHashLength, + #[error("inclusion proof failed {0:?}")] + InclusionProofFailed(ProofError), + #[error("consistency proof failed {0:?}")] + ConsistencyProofFailed(ProofError), + #[error("failed to decode PEM key")] + PemKeyDecodingError, +} diff --git a/bt2x/bt2x-common/src/gossip.rs b/bt2x/bt2x-common/src/gossip.rs new file mode 100644 index 0000000..093e77a --- /dev/null +++ b/bt2x/bt2x-common/src/gossip.rs @@ -0,0 +1,278 @@ +//! ## Example: parsing and verifying a Checkpoint +//! +//!```ignore +//! use bt2x_common::gossip::{Checkpoint, send_checkpoint}; +//! #[tokio::main] +//! async fn main() { +//! // parse a checkpoint +//! let checkpoint: Checkpoint = "rekor.sigstore.dev - 2605736670972794746\n16895256\n/pOURNyljCZ3+Se0BHOmRJfTix2FC32SbGpRcMlUdwI=\nTimestamp: 1684488982407313166\n\n— rekor.sigstore.dev wNI9ajBEAiALqNNUxhyD9Ja38iUUMWNNI7mNGZO0qGrmDsdVLhxXxwIgBqn7Dnjqr2INJJ/VAovLgNBORFa5rRIwPQUcIska7n4=\n" +//! .parse() +//! .expect("failed to parse checkpoint"); +//! +//! // send the checkpoint to a monitor +//! send_checkpoint( +//! &url::Url::parse("https://example.net").unwrap(), +//! &checkpoint, +//! ) +//! .await +//! .expect("failure during gossiping"); +//! } +//! ``` + +//! + +use crate::error::Bt2XError; +use crate::rekor::RekorClient; +use crate::verifier::bt::verify_consistency; +use anyhow::{anyhow, Context}; +use base64::prelude::BASE64_STANDARD; +use base64::Engine; +use serde::{Deserialize, Serialize}; +use sigstore::crypto::{CosignVerificationKey, Signature}; +use std::cmp::Ordering; +use std::str::FromStr; +use tracing::debug; +use url::Url; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct Gossip { + pub checkpoint: Checkpoint, +} + +/// Data structure to represent a parsed checkpoint. +/// The following example shows a the encoding of a checkpoint, as distributed by the log. +/// ``` +/// use bt2x_common::gossip::Checkpoint; +/// +/// let checkpoint: Checkpoint = "rekor.sigstore.dev - 2605736670972794746\n16895256\n/pOURNyljCZ3+Se0BHOmRJfTix2FC32SbGpRcMlUdwI=\nTimestamp: 1684488982407313166\n\n— rekor.sigstore.dev wNI9ajBEAiALqNNUxhyD9Ja38iUUMWNNI7mNGZO0qGrmDsdVLhxXxwIgBqn7Dnjqr2INJJ/VAovLgNBORFa5rRIwPQUcIska7n4=\n" +/// .parse() +/// .expect("failed to parse checkpoint"); +/// ``` +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct Checkpoint { + pub root_hash: [u8; 32], + pub tree_size: usize, + pub key_fingerprint: [u8; 4], + pub sig: Vec, + pub timestamp: i64, + pub identity: String, + pub tree_id: i64, +} + +impl FromStr for Checkpoint { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + let s = s.trim_start_matches('"').trim_end_matches('"'); + let checkpoint = s; + let [data, sig] = checkpoint.split("\n\n").collect::>()[..] else { + return Err(anyhow!("checkpoint did not split correctly {s:?}")); + }; + let [_, name, sig_b64] = sig.split(' ').collect::>()[..] else { + return Err(anyhow!("signature did not split correctly {s:?}")); + }; + let sig = BASE64_STANDARD + .decode(sig_b64.trim_end()) + .context("failed to decode signature")?; + // first four bytes of signature are fingerprint of key + let (key_fingerprint, sig) = sig.split_at(4); + + let [tree, size, root_hash_b64, ts] = data.split('\n').collect::>()[..] else { + return Err(anyhow!("data did not split correctly {data:?}")); + }; + let [identity, _, tree_id] = tree.split(' ').collect::>()[..] else { + return Err(anyhow!("identity did not split correctly")); + }; + let root_hash = BASE64_STANDARD + .decode(root_hash_b64) + .context("failed to decode root hash") + .and_then(|v| { + <[u8; 32]>::try_from(v).map_err(|err| anyhow!("could not convert hash {err:?}")) + })?; + + let tree_size = size.parse().context("could not parse tree size")?; + let ts = ts + .trim_start_matches("Timestamp: ") + .parse() + .context("could not parse timestamp")?; + let tree_id = tree_id.parse().context("could not parse tree_id")?; + + if name != identity { + return Err(anyhow!("mismatching information regarding identity of log")); + } + Ok(Checkpoint { + timestamp: ts, + tree_size, + root_hash, + tree_id, + identity: identity.into(), + sig: sig.into(), + key_fingerprint: key_fingerprint.try_into().expect("this should never fail"), + }) + } +} + +impl Checkpoint { + pub fn to_signed_note(&self) -> String { + let Checkpoint { + identity, + sig, + key_fingerprint, + .. + } = self; + let sig_b64 = BASE64_STANDARD.encode([key_fingerprint.as_slice(), sig.as_slice()].concat()); + let note = self.create_note(); + format!("{note}\n— {identity} {sig_b64}\n") + } +} + +impl Checkpoint { + /// Create a so called `note` from the checkpoint. + /// Which is the encoding of the checkpoint used to create signatures. + pub fn create_note(&self) -> String { + let Checkpoint { + timestamp, + identity: name, + root_hash, + tree_size, + tree_id, + .. + } = self; + let root_hash_b64 = BASE64_STANDARD.encode(root_hash); + format!("{name} - {tree_id}\n{tree_size}\n{root_hash_b64}\nTimestamp: {timestamp}\n") + } + + /// Verifies that the checkpoint was signed with the given Rekor key. + pub fn verify_signature(&self, key: &CosignVerificationKey) -> Result<(), Bt2XError> { + key.verify_signature(Signature::Raw(&self.sig), self.create_note().as_bytes()) + .map_err(|_| Bt2XError::CheckpointSignatureValidationFailed) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum GossipResponse { + Success(Checkpoint), + Failure(MonitorError), +} + +/// Fetch a checkpoint from the log and verify its signature. +pub async fn fetch_and_verify_signature( + rekor_client: &RekorClient, + rekor_key: &CosignVerificationKey, +) -> Result { + // Fetch log info, parse it and then verify it using the provided key. + rekor_client + .get_log_info() + .await + .map_err(Bt2XError::FailedToFetchLogInfo) + .and_then(|log_info| { + log_info + .signed_tree_head + .parse::() + .map_err(Bt2XError::FailedToParseLogCheckpoint) + }) + .and_then(|checkpoint| { + checkpoint.verify_signature(rekor_key)?; + Ok(checkpoint) + }) +} + +/// Verify that two checkpoints are consistent by requesting the corresponding consistency proof from the log. +pub async fn verify_checkpoints( + trusted_checkpoint: Checkpoint, + requested_checkpoint: Checkpoint, + rekor_client: &RekorClient, +) -> Result { + let (old, new) = match Ord::cmp( + &trusted_checkpoint.tree_size, + &requested_checkpoint.tree_size, + ) { + Ordering::Less => { + debug!("requested checkpoint is newer than trusted checkpoint"); + (trusted_checkpoint, requested_checkpoint) + } + Ordering::Equal | Ordering::Greater => { + debug!("requested checkpoint is older than trusted checkpoint"); + (requested_checkpoint, trusted_checkpoint) + } + }; + debug!( + "fetching log proof for: {:?}", + (new.tree_size, Some(old.tree_size)) + ); + let proof = rekor_client + .get_log_proof(new.tree_size, Some(old.tree_size), None) + .await + .map_err(Bt2XError::FailedToFetchLogProof)?; + debug!("successfully fetched proof from log"); + verify_consistency( + old.tree_size, + new.tree_size, + &proof, + &old.root_hash, + &new.root_hash, + ) + .map(|_| new) +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, thiserror::Error)] +pub enum MonitorError { + #[error("verifying {request_data:?} using {pubkey:?} failed")] + FailedSignatureVerification { + pubkey: (), + request_data: Checkpoint, + }, + #[error("verifying consistency of {request_data:?} using {other:?} failed")] + Inconsistent { + other: Checkpoint, + request_data: Checkpoint, + }, +} + +/// Send a checkpoint to the Rekor monitor at the provided URL. +pub async fn send_checkpoint( + server_url: &Url, + current_checkpoint: &Checkpoint, +) -> Result { + let response = reqwest::Client::new() + .post(server_url.join("/listen").unwrap()) + .json(current_checkpoint) + .send() + .await + .map_err(Bt2XError::GossipingCheckpointsFailed)?; + + response + .json::() + .await + .map_err(Bt2XError::DeserializingGossipResponseFailed) +} + +#[cfg(test)] +mod test_parse { + use super::*; + use hex_literal::hex; + use sigstore::crypto::CosignVerificationKey; + + #[test] + fn test_parse_checkpoint() { + let checkpoint = "rekor.sigstore.dev - 2605736670972794746\n16895256\n/pOURNyljCZ3+Se0BHOmRJfTix2FC32SbGpRcMlUdwI=\nTimestamp: 1684488982407313166\n\n— rekor.sigstore.dev wNI9ajBEAiALqNNUxhyD9Ja38iUUMWNNI7mNGZO0qGrmDsdVLhxXxwIgBqn7Dnjqr2INJJ/VAovLgNBORFa5rRIwPQUcIska7n4=\n"; + let expected = Checkpoint { + root_hash: hex!("fe939444dca58c2677f927b40473a64497d38b1d850b7d926c6a5170c9547702"), + tree_size: 16895256, + key_fingerprint: hex!("c0d23d6a"), + timestamp: 1684488982407313166, + identity: "rekor.sigstore.dev".to_string(), + tree_id: 2605736670972794746, + sig: From::from(hex!("304402200ba8d354c61c83f496b7f2251431634d23b98d1993b4a86ae60ec7552e1c57c7022006a9fb0e78eaaf620d249fd5028bcb80d04e4456b9ad12303d051c22c91aee7e")), + }; + let rekor_key = CosignVerificationKey::from_pem( + b"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr\nkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==\n-----END PUBLIC KEY-----", + &Default::default(), + ).expect("failed to parse rekor key"); + + let output: Checkpoint = checkpoint.parse().unwrap(); + assert_eq!(output, expected); + assert_eq!(output.to_signed_note(), checkpoint); + output.verify_signature(&rekor_key).unwrap(); + } +} diff --git a/bt2x/bt2x-common/src/lib.rs b/bt2x/bt2x-common/src/lib.rs new file mode 100644 index 0000000..cf4eb02 --- /dev/null +++ b/bt2x/bt2x-common/src/lib.rs @@ -0,0 +1,21 @@ +//! # BT²X Common +//! This crate contains functionality that is meant to be shared across different components. + +pub mod artifact; +pub mod error; +/// Gossiping primitives. +pub mod gossip; +/// Merkle proof implementations. +pub mod merkle; +/// Custom Rekor data types. +pub mod rekor; +/// SCT validation. +pub mod sct; +/// Serde data structures. +pub mod serde; +/// Data structures related to configuring Sigstore related applications. +pub mod sigstore_config; +/// TUF integration into BT²X. +pub mod tuf; +/// Verification implementation. +pub mod verifier; diff --git a/bt2x/bt2x-common/src/merkle/mod.rs b/bt2x/bt2x-common/src/merkle/mod.rs new file mode 100644 index 0000000..13bc3fd --- /dev/null +++ b/bt2x/bt2x-common/src/merkle/mod.rs @@ -0,0 +1,6 @@ +pub mod rfc6962; +pub mod tree; +pub mod verify; + +pub use rfc6962::{Rfc6269Default, Rfc6269HasherTrait}; +pub use verify::MerkleProofVerifier; diff --git a/bt2x/bt2x-common/src/merkle/rfc6962.rs b/bt2x/bt2x-common/src/merkle/rfc6962.rs new file mode 100644 index 0000000..8faa031 --- /dev/null +++ b/bt2x/bt2x-common/src/merkle/rfc6962.rs @@ -0,0 +1,103 @@ +use digest::Output; +use sha2::{Digest, Sha256}; + +#[repr(u8)] +pub enum Rfc6269HashPrefix { + RFC6962LeafHashPrefix = 0, + RFC6962NodeHashPrefix = 1, +} + +pub trait Rfc6269HasherTrait { + fn empty_root() -> O; + fn hash_leaf(leaf: &[u8]) -> O; + fn hash_children(left: &[u8], right: &[u8]) -> O; +} + +impl Rfc6269HasherTrait> for T +where + T: Digest, +{ + fn empty_root() -> Output { + T::new().finalize() + } + fn hash_leaf(leaf: &[u8]) -> Output { + T::new() + .chain_update([Rfc6269HashPrefix::RFC6962LeafHashPrefix as u8]) + .chain_update(leaf) + .finalize() + } + fn hash_children(left: &[u8], right: &[u8]) -> Output { + T::new() + .chain_update([Rfc6269HashPrefix::RFC6962NodeHashPrefix as u8]) + .chain_update(left) + .chain_update(right) + .finalize() + } +} + +pub type Rfc6269Default = Sha256; + +#[cfg(test)] +mod test_rfc6962 { + use crate::merkle::rfc6962::Rfc6269Default; + use crate::merkle::rfc6962::Rfc6269HasherTrait; + use hex_literal::hex; + + #[derive(Debug, PartialEq)] + struct TestCase { + pub desc: String, + pub got: [u8; 32], + pub want: [u8; 32], + } + + #[test] + fn test_hasher() { + let leaf_hash = Rfc6269Default::hash_leaf(b"L123456"); + let empty_leaf_hash = Rfc6269Default::hash_leaf(b""); + let test_cases: Vec<_> = [ + TestCase { + desc: "RFC6962 Empty".to_string(), + want: hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), + got: Rfc6269Default::empty_root().into(), + }, + TestCase { + desc: "RFC6962 Empty Leaf".to_string(), + want: hex!("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"), + got: empty_leaf_hash.into(), + }, + TestCase { + desc: "RFC6962 Leaf".to_string(), + want: hex!("395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"), + got: leaf_hash.into(), + }, + TestCase { + desc: "RFC6962 Node".to_string(), + want: hex!("aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"), + got: Rfc6269Default::hash_children(b"N123", b"N456").into(), + }, + ] + .into_iter() + .filter(|tc| tc.got != tc.want) + .collect(); + assert_eq!(test_cases.len(), 0, "failed tests: {test_cases:?}") + } + + #[test] + fn test_collisions() { + let l1 = b"Hello".to_vec(); + let l2 = b"World".to_vec(); + let hash1 = Rfc6269Default::hash_leaf(&l1); + let hash2 = Rfc6269Default::hash_leaf(&l2); + assert_ne!(hash1, hash2, "got identical hashes for different leafs"); + + let sub_hash1 = Rfc6269Default::hash_children(&l1, &l2); + let sub_hash2 = Rfc6269Default::hash_children(&l2, &l1); + assert_ne!(sub_hash1, sub_hash2, "got same hash for different order"); + + let forged_hash = Rfc6269Default::hash_leaf(&[l1, l2].concat()); + assert_ne!( + sub_hash1, forged_hash, + "hasher is not second-preimage resistant" + ); + } +} diff --git a/bt2x/bt2x-common/src/merkle/tree.rs b/bt2x/bt2x-common/src/merkle/tree.rs new file mode 100644 index 0000000..5ef5a91 --- /dev/null +++ b/bt2x/bt2x-common/src/merkle/tree.rs @@ -0,0 +1,95 @@ +use crate::merkle::rfc6962::{Rfc6269Default, Rfc6269HasherTrait}; +use digest::Output; + +pub fn calc_tree(entries: &[impl AsRef<[u8]>]) -> Output { + if entries.is_empty() { + return Rfc6269Default::empty_root(); + } + let leaf_hashes: Vec<_> = entries + .iter() + .map(|entry| Rfc6269Default::hash_leaf(entry.as_ref())) + .collect(); + tree_rec(&leaf_hashes) +} + +fn tree_rec(children: &[Output]) -> Output { + match children { + [leaf] => *leaf, + [left, right] => Rfc6269Default::hash_children(left.as_slice(), right.as_slice()), + many => { + let (left, right) = many.split_at((many.len() - 1) / 2 + 1); + tree_rec(&[tree_rec(left), tree_rec(right)]) + } + } +} + +#[cfg(test)] +mod test_tree { + use crate::merkle::rfc6962::{Rfc6269Default, Rfc6269HasherTrait}; + use crate::merkle::tree::calc_tree; + + #[test] + pub fn test_0() { + let input: Vec<&[u8]> = vec![]; + let hash = calc_tree(&input); + assert_eq!(hash, Rfc6269Default::empty_root()) + } + + #[test] + pub fn test_1() { + let input = [[0_u8]]; + let hash = calc_tree(&input); + assert_eq!(hash, Rfc6269Default::hash_leaf([0].as_slice())) + } + + #[test] + pub fn test_2() { + let input = vec![[0], [1]]; + let hash = calc_tree(&input); + assert_eq!( + hash, + Rfc6269Default::hash_children( + Rfc6269Default::hash_leaf([0].as_slice()).as_slice(), + Rfc6269Default::hash_leaf([1].as_slice()).as_slice(), + ) + ) + } + + #[test] + pub fn test_3() { + let input = vec![[0], [1], [2]]; + let hash = calc_tree(&input); + assert_eq!( + hash, + Rfc6269Default::hash_children( + Rfc6269Default::hash_children( + Rfc6269Default::hash_leaf([0].as_slice()).as_slice(), + Rfc6269Default::hash_leaf([1].as_slice()).as_slice(), + ) + .as_slice(), + Rfc6269Default::hash_leaf([2].as_slice()).as_slice(), + ) + ) + } + + #[test] + pub fn test_4() { + let input = vec![[0], [1], [2], [3]]; + let hash = calc_tree(&input); + assert_eq!( + hash, + Rfc6269Default::hash_children( + Rfc6269Default::hash_children( + Rfc6269Default::hash_leaf([0].as_slice()).as_slice(), + Rfc6269Default::hash_leaf([1].as_slice()).as_slice(), + ) + .as_slice(), + Rfc6269Default::hash_children( + Rfc6269Default::hash_leaf([2].as_slice()).as_slice(), + Rfc6269Default::hash_leaf([3].as_slice()).as_slice(), + ) + .as_slice(), + ) + ) + } +} diff --git a/bt2x/bt2x-common/src/merkle/verify.rs b/bt2x/bt2x-common/src/merkle/verify.rs new file mode 100644 index 0000000..4d4993c --- /dev/null +++ b/bt2x/bt2x-common/src/merkle/verify.rs @@ -0,0 +1,178 @@ +use crate::merkle::rfc6962::Rfc6269HasherTrait; +use crate::merkle::verify::ProofError::*; +use digest::{Digest, Output}; +use std::cmp::Ordering; +use std::fmt::Debug; + +#[derive(Debug)] +pub enum ProofError { + MismatchedRoot { expected: String, got: String }, + IndexGtTreeSize, + UnexpectedNonEmptyProof, + UnexpectedEmptyProof, + NewTreeSmaller { new: usize, old: usize }, + WrongProofSize { got: usize, want: usize }, +} + +pub trait MerkleProofVerifier: Rfc6269HasherTrait +where + O: Eq + AsRef<[u8]> + Clone + Debug, +{ + #[allow(clippy::result_unit_err)] + fn verify_match(a: &O, b: &O) -> Result<(), ()> { + (a == b).then_some(()).ok_or(()) + } + + fn verify_inclusion( + index: usize, + leaf_hash: &O, + tree_size: usize, + proof_hashes: &[O], + root_hash: &O, + ) -> Result<(), ProofError> { + if index >= tree_size { + return Err(IndexGtTreeSize); + } + Self::root_from_inclusion_proof(index, leaf_hash, tree_size, proof_hashes).and_then( + |calc_root| { + Self::verify_match(calc_root.as_ref(), root_hash).map_err(|_| MismatchedRoot { + got: hex::encode(root_hash), + expected: hex::encode(*calc_root), + }) + }, + ) + } + + fn root_from_inclusion_proof( + index: usize, + leaf_hash: &O, + tree_size: usize, + proof_hashes: &[O], + ) -> Result, ProofError> { + if index >= tree_size { + return Err(IndexGtTreeSize); + } + let (inner, border) = Self::decomp_inclusion_proof(index, tree_size); + match (proof_hashes.len(), inner + border) { + (got, want) if got != want => { + return Err(WrongProofSize { + got: proof_hashes.len(), + want: inner + border, + }) + } + _ => {} + } + + let res_left = Self::chain_inner(leaf_hash, &proof_hashes[..inner], index); + let res = Self::chain_border_right(&res_left, &proof_hashes[inner..]); + Ok(Box::new(res)) + } + + fn verify_consistency( + old_size: usize, + new_size: usize, + proof_hashes: &[O], + old_root: &O, + new_root: &O, + ) -> Result<(), ProofError> { + match Ord::cmp(&old_size, &new_size) { + Ordering::Greater => { + return Err(NewTreeSmaller { + new: new_size, + old: old_size, + }) + } + Ordering::Equal if Self::verify_match(new_root, old_root).is_err() => { + return Err(MismatchedRoot { + got: hex::encode(new_root), + expected: hex::encode(old_root), + }) + } + Ordering::Equal if old_size == new_size && proof_hashes.is_empty() => { + return Ok(()); + } + _ => {} + }; + match (new_size == old_size, proof_hashes.is_empty()) { + (true, false) => return Err(UnexpectedNonEmptyProof), + (false, true) => return Err(UnexpectedEmptyProof), + (true, true) => { + unreachable!("this should be unreachable"); + } + _ => {} + } + let (mut inner, border) = Self::decomp_inclusion_proof(old_size - 1, new_size); + let shift = old_size.trailing_zeros() as usize; + inner -= shift; + + let mut seed = &proof_hashes[0]; + let mut start = 1; + if old_size == 1 << shift { + seed = old_root; + start = 0; + } + let got = proof_hashes.len(); + let want = start + inner + border; + if got != want { + return Err(WrongProofSize { got, want }); + } + let proof = &proof_hashes[start..]; + let mask = (old_size - 1) >> shift; + let hash1 = Self::chain_inner_right(seed, &proof[..inner], mask); + let hash1 = Self::chain_border_right(&hash1, &proof[inner..]); + Self::verify_match(&hash1, old_root).map_err(|_| MismatchedRoot { + got: hex::encode(old_root), + expected: hex::encode(hash1), + })?; + let hash2 = Self::chain_inner(seed, &proof[..inner], mask); + let hash2 = Self::chain_border_right(&hash2, &proof[inner..]); + Self::verify_match(&hash2, new_root).map_err(|_| MismatchedRoot { + got: hex::encode(new_root), + expected: hex::encode(old_root), + })?; + Ok(()) + } + fn chain_inner(seed: &O, proof_hashes: &[O], index: usize) -> O { + proof_hashes + .iter() + .enumerate() + .fold(seed.clone(), |seed, (i, h)| { + if (index >> i) & 1 == 0 { + Self::hash_children(seed.as_ref(), h.as_ref()) + } else { + Self::hash_children(h.as_ref(), seed.as_ref()) + } + }) + } + + fn chain_inner_right(seed: &O, proof_hashes: &[O], index: usize) -> O { + proof_hashes + .iter() + .enumerate() + .fold(seed.clone(), |seed, (i, h)| { + if (index >> i) & 1 == 1 { + Self::hash_children(h.as_ref(), seed.as_ref()) + } else { + seed + } + }) + } + + fn chain_border_right(seed: &O, proof_hashes: &[O]) -> O { + proof_hashes.iter().fold(seed.clone(), |seed, h| { + Self::hash_children(h.as_ref(), seed.as_ref()) + }) + } + + fn decomp_inclusion_proof(index: usize, tree_size: usize) -> (usize, usize) { + let inner: usize = Self::inner_proof_size(index, tree_size); + let border = (index >> inner).count_ones() as usize; + (inner, border) + } + + fn inner_proof_size(index: usize, tree_size: usize) -> usize { + u64::BITS as usize - ((index ^ (tree_size - 1)).leading_zeros() as usize) + } +} + +impl MerkleProofVerifier> for T where T: Digest {} diff --git a/bt2x/bt2x-common/src/rekor.rs b/bt2x/bt2x-common/src/rekor.rs new file mode 100644 index 0000000..6bf00c5 --- /dev/null +++ b/bt2x/bt2x-common/src/rekor.rs @@ -0,0 +1,106 @@ +pub use sigstore::rekor::apis::configuration::Configuration; +use sigstore::rekor::apis::entries_api::{CreateLogEntryError, SearchLogQueryError}; +use sigstore::rekor::apis::index_api::SearchIndexError; +use sigstore::rekor::apis::tlog_api::{GetLogInfoError, GetLogProofError}; +use sigstore::rekor::apis::{entries_api, index_api, pubkey_api, tlog_api, Error}; +use sigstore::rekor::models::{ + ConsistencyProof, LogEntry, LogInfo, ProposedEntry, SearchIndex, SearchLogQuery, +}; + +pub enum RekorEntryIdentifier { + LogIndex(u64), + Uuid(String), +} + +impl From for RekorEntryIdentifier { + fn from(val: u64) -> RekorEntryIdentifier { + RekorEntryIdentifier::LogIndex(val) + } +} + +impl From<&str> for RekorEntryIdentifier { + fn from(val: &str) -> RekorEntryIdentifier { + RekorEntryIdentifier::Uuid(val.to_string()) + } +} + +#[derive(Debug)] +pub struct RekorClient { + config: Configuration, +} + +impl RekorClient { + pub async fn get_log_entry( + &self, + identifier: impl Into, + ) -> Result> { + match identifier.into() { + RekorEntryIdentifier::LogIndex(log_index) => { + if log_index == 0 { + return Err("log index is always > 0".to_string().into()); + } + entries_api::get_log_entry_by_index(&self.config, log_index as i32) + .await + .map_err(|err| err.into()) + } + RekorEntryIdentifier::Uuid(uiid) => { + entries_api::get_log_entry_by_uuid(&self.config, uiid.as_str()) + .await + .map_err(|err| err.into()) + } + } + } + + pub async fn create_log_entry( + &self, + proposed_entry: ProposedEntry, + ) -> Result> { + entries_api::create_log_entry(&self.config, proposed_entry).await + } + + pub async fn search_log_query( + &self, + query: SearchLogQuery, + ) -> Result> { + entries_api::search_log_query(&self.config, query).await + } + + pub async fn get_log_info(&self) -> Result> { + tlog_api::get_log_info(&self.config).await + } + + pub async fn get_log_proof( + &self, + last_size: usize, + first_size: Option, + tree_id: Option<&str>, + ) -> Result> { + let first_size = first_size.map(|i| i.to_string()); + tlog_api::get_log_proof( + &self.config, + last_size as i32, + first_size.as_deref(), + tree_id, + ) + .await + } + + pub async fn search_index( + &self, + query: SearchIndex, + ) -> Result, Error> { + index_api::search_index(&self.config, query).await + } + + pub async fn get_pubkey( + &self, + tree_id: Option<&str>, + ) -> Result> { + pubkey_api::get_public_key(&self.config, tree_id) + .await + .map_err(|err| err.into()) + } + pub fn new(config: Configuration) -> Self { + RekorClient { config } + } +} diff --git a/bt2x/bt2x-common/src/sct.rs b/bt2x/bt2x-common/src/sct.rs new file mode 100644 index 0000000..cbdc44d --- /dev/null +++ b/bt2x/bt2x-common/src/sct.rs @@ -0,0 +1,59 @@ +use digest::FixedOutput; +use p256::ecdsa::VerifyingKey as P256VerifyingKey; +use sct::{verify_sct, Log}; +use sha2::{Digest, Sha256}; +use spki::{DecodePublicKey, EncodePublicKey, ObjectIdentifier}; +use std::time::UNIX_EPOCH; +use x509_cert::der::{DecodePem, Encode}; + +use crate::error::Bt2XError; + +const CT_PRECERT_SCTS_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11129.2.4.2"); + +pub fn validate_sct_pem(cert_pem: &[u8], ctlog_keys: &[&[u8]]) -> Result<(), Bt2XError> { + let cert = x509_cert::Certificate::from_pem(cert_pem).expect("failed to parse cert"); + let sct = cert + .tbs_certificate + .extensions + .as_ref() + .ok_or(Bt2XError::CertificateIsMissingCtExtensions)? + .iter() + .find(|e| e.extn_id == CT_PRECERT_SCTS_OID) + .ok_or(Bt2XError::CertificateIsMissingCtExtensions)?; + let ctlog_keys = ctlog_keys + .iter() + .map(|&b| { + P256VerifyingKey::from_public_key_pem(std::str::from_utf8(b).unwrap()) + .expect("failed to parse key") + }) + .map(|key| { + let key_der = key.to_public_key_der().unwrap().as_bytes().to_vec(); + let key_id: [u8; 32] = Sha256::new().chain_update(&key_der).finalize_fixed().into(); + (key_der, key_id, "".to_string()) + }) + .collect::>(); + + let ctlogs = ctlog_keys + .iter() + .map(|(key, key_id, s)| Log { + description: s, + url: s, + operated_by: s, + key: key.as_slice(), + id: *key_id, + max_merge_delay: 0, + }) + .collect::>(); + verify_sct( + cert.to_der().unwrap().as_slice(), + sct.extn_value.as_bytes(), + std::time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ctlogs.iter().collect::>().as_slice(), + ) + .map(|_| ()) + .map_err(Bt2XError::SctValidationFailed) +} diff --git a/bt2x/bt2x-common/src/serde.rs b/bt2x/bt2x-common/src/serde.rs new file mode 100644 index 0000000..470969e --- /dev/null +++ b/bt2x/bt2x-common/src/serde.rs @@ -0,0 +1,225 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_with::{base64::Base64, hex::Hex, serde_as}; + +use base64::{engine::general_purpose::STANDARD as B64StandardEngine, Engine}; +use olpc_cjson::CanonicalFormatter; +use serde::de::DeserializeOwned; +use sigstore::cosign::bundle::{Payload, SignedArtifactBundle}; +use sigstore::rekor::models::LogEntry; + +use crate::error::Bt2XError; + +#[serde_as] +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CosignBundle { + #[serde_as(as = "Base64")] + base64_signature: Vec, + #[serde_as(as = "Base64")] + cert: Vec, + rekor_bundle: RekorBundle, +} + +#[serde_as] +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct RekorBundle { + #[serde_as(as = "Base64")] + pub signed_entry_timestamp: Vec, + pub payload: BundlePayload, +} + +#[serde_as] +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BundlePayload { + #[serde_as(as = "Base64")] + pub body: Vec, + pub integrated_time: u64, + pub log_index: u64, + #[serde_as(as = "Hex")] + #[serde(rename = "logID")] + pub log_id: [u8; 32], +} + +#[serde_as] +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CompactRekorBundle { + #[serde_as(as = "Base64")] + pub signed_entry_timestamp: Vec, + #[serde( + serialize_with = "serialize_b64_canonical_json", + deserialize_with = "deserialize_b64_canonical_json" + )] + pub payload: CompactBundlePayload, +} + +#[serde_as] +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CompactBundlePayload { + //#[serde_as(as = "Base64")] + #[serde( + serialize_with = "serialize_b64_canonical_json", + deserialize_with = "deserialize_b64_canonical_json" + )] + pub body: CompactPayloadBody, + + pub integrated_time: u64, + pub log_index: u64, + #[serde_as(as = "Hex")] + #[serde(rename = "logID")] + pub log_id: [u8; 32], +} + +#[serde_as] +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CompactPayloadBody { + pub api_version: ApiVersion, + #[serde(flatten)] + pub spec: Spec, +} + +#[derive(Deserialize, Serialize)] +#[serde(tag = "kind", content = "spec")] +pub enum Spec { + #[serde(rename = "hashedrekord")] + HashedRekord { + data: HashedRekordData, + signature: Signature, + }, +} + +#[derive(Deserialize, Serialize)] +pub struct HashedRekordData { + hash: Hash, +} + +#[serde_as] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Signature { + #[serde(skip_serializing_if = "Option::is_none")] + pub format: Option, + #[serde_as(as = "Base64")] + pub content: Vec, + pub public_key: PublicKey, +} + +#[serde_as] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKey { + #[serde_as(as = "Base64")] + pub content: Vec, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SignatureFormat { + Pgp, + Minisign, + X509, + Ssh, +} + +#[serde_as] +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase", tag = "algorithm", content = "value")] +pub enum Hash { + Sha256(#[serde_as(as = "Hex")] [u8; 32]), +} + +#[derive(Deserialize, Serialize)] +pub enum ApiVersion { + #[serde(rename = "0.0.1")] + ZeroZeroOne, +} + +fn serialize_b64_canonical_json(payload: &T, s: S) -> Result +where + S: Serializer, + T: Serialize, +{ + let mut buf = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, CanonicalFormatter::new()); + payload + .serialize(&mut ser) + .map_err(|err| serde::ser::Error::custom(format!("{err}"))) + .map(|_| B64StandardEngine.encode(buf)) + .and_then(|encoded| s.serialize_str(&encoded)) +} + +fn deserialize_b64_canonical_json<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: DeserializeOwned, +{ + let buf = String::deserialize(deserializer)?; + B64StandardEngine + .decode(buf) + .map_err(|err| err.to_string()) + .and_then(|decoded| serde_json::from_slice::(&decoded).map_err(|e| e.to_string())) + .map_err(serde::de::Error::custom) +} + +impl From for CompactRekorBundle { + fn from(value: SignedArtifactBundle) -> Self { + CompactRekorBundle { + signed_entry_timestamp: B64StandardEngine + .decode(value.rekor_bundle.signed_entry_timestamp) + .expect("failed to decode the SET"), + payload: value.rekor_bundle.payload.into(), + } + } +} + +impl CosignBundle { + pub fn from_signing_material_and_log_entry( + sig: &[u8], + cert_pem: &[u8], + log_entry: &LogEntry, + ) -> Result { + Ok(CosignBundle { + base64_signature: (B64StandardEngine.encode(sig)).into_bytes(), + cert: B64StandardEngine.encode(cert_pem).into_bytes(), + rekor_bundle: RekorBundle { + signed_entry_timestamp: log_entry + .verification + .signed_entry_timestamp + .clone() + .into_bytes(), + payload: BundlePayload { + body: serde_json::to_vec(&log_entry.body) + .map_err(Bt2XError::JsonSerializationError) + .map(|v| B64StandardEngine.encode(v).into_bytes())?, + integrated_time: log_entry.integrated_time as u64, + log_index: log_entry.log_index as u64, + log_id: (hex::decode(&log_entry.log_i_d) + .map_err(Bt2XError::HexDecodingError)?) + .try_into() + .map_err(|_| { + Bt2XError::InternalError("could not convert slice to Array".to_string()) + })?, + }, + }, + }) + } +} + +impl From for CompactBundlePayload { + fn from(value: Payload) -> Self { + CompactBundlePayload { + body: serde_json::from_slice(serde_json::to_vec(&value.body).unwrap().as_slice()) + .unwrap(), + integrated_time: value.integrated_time as u64, + log_index: value.log_index as u64, + log_id: <[u8; 32]>::try_from( + hex::decode(value.log_id).expect("failed to decode the log ID"), + ) + .expect("invalid length"), + } + } +} diff --git a/bt2x/bt2x-common/src/sigstore_config.rs b/bt2x/bt2x-common/src/sigstore_config.rs new file mode 100644 index 0000000..eb6f2a4 --- /dev/null +++ b/bt2x/bt2x-common/src/sigstore_config.rs @@ -0,0 +1,137 @@ +//! ## Example YAML Configuration +//! +//! ``` +//! use bt2x_common::sigstore_config::SigstoreConfig; +//! +//! let config = r#" +//! key_config: +//! !tuf +//! root_path: "/path/to/root.json" +//! metadata_base: "https://example.org/path" +//! targets_base: "file://example.org/path/targets" +//! target_names: +//! rekor: "rekor.pub" +//! fulcio: "fulcio.crt" +//! ctlog: "ctlog.pub" +//! +//! urls: +//! rekor: http://rekor.example.org +//! fulcio: http://fulcio.example.org +//! oidc_issuer: http://fulcio.example.org +//! "#; +//! let config: SigstoreConfig = serde_yaml::from_str(config).expect("failed to parse config"); +//! ``` + +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use url::Url; + +/// Data structure that is used to configure Sigstore with YAML files. +/// ``` +/// use bt2x_common::sigstore_config::SigstoreConfig; +/// +/// let config = r#" +/// key_config: +/// !tuf +/// root_path: "/path/to/root.json" +/// metadata_base: "https://example.org/path" +/// targets_base: "file://example.org/path/targets" +/// target_names: +/// rekor: "rekor.pub" +/// fulcio: "fulcio.crt" +/// ctlog: "ctlog.pub" +/// +/// urls: +/// rekor: http://rekor.example.org +/// fulcio: http://fulcio.example.org +/// oidc_issuer: http://fulcio.example.org +/// "#; +/// let config: SigstoreConfig = serde_yaml::from_str(config).expect("failed to parse config"); +/// ``` +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct SigstoreConfig { + pub urls: SigstoreUrls, + pub key_config: KeyConfig, +} + +/// Enum that is used to configure the keys that are used for Sigstore. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum KeyConfig { + /// specify a TUF repo from which the Sigstore keys are loaded + Tuf { + /// path to the INITIAL root file + root_path: PathBuf, + /// path/URL from which updates to the TUF metadata is fetched + metadata_base: Url, + /// path/URL from which updates to the TUF target files are fetched + targets_base: Url, + /// file names of the TUF target files + target_names: TufTargetNames, + }, + /// specify the Sigstore keys via file path + Keys { + rekor_key: Option, + fulcio_cert: Option, + ctlog_key: Option, + }, +} + +/// Struct used to specify the URLs of Sigstore servers. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub struct SigstoreUrls { + pub rekor: Url, + pub fulcio: Url, + pub oidc_issuer: Url, +} + +fn rekor_target_default() -> String { + "rekor.pub".to_string() +} + +fn fulcio_target_default() -> String { + "fulcio.crt".to_string() +} + +fn ctlog_target_default() -> String { + "ctfe.pub".to_string() +} + +/// Struct used to specify the file names of Sigstore targets in the TUF repo. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub struct TufTargetNames { + #[serde(default = "rekor_target_default")] + pub rekor: String, + #[serde(default = "fulcio_target_default")] + pub fulcio: String, + #[serde(default = "ctlog_target_default")] + pub ctlog: String, + //#[serde(flatten)] + //extra_targets: HashMap, +} + +#[cfg(test)] +mod test { + use crate::sigstore_config::SigstoreConfig; + + #[test] + fn test_parse_config() { + let config = r#" +key_config: + !tuf + root_path: "file://example.org/root.json" + metadata_base: "https://example.org/path" + targets_base: "file://example.org/path" + target_names: + rekor: "rekor.pub" + fulcio: "fulcio.crt" + ctlog: "ctlog.pub" + +urls: + rekor: http://rekor.example.org + fulcio: http://fulcio.example.org + oidc_issuer: http://fulcio.example.org +"#; + let _: SigstoreConfig = serde_yaml::from_str(config).expect("failed to parse config"); + } +} diff --git a/bt2x/bt2x-common/src/tuf/mod.rs b/bt2x/bt2x-common/src/tuf/mod.rs new file mode 100644 index 0000000..8ba5a80 --- /dev/null +++ b/bt2x/bt2x-common/src/tuf/mod.rs @@ -0,0 +1,142 @@ +use crate::error::Bt2XError; +use crate::sigstore_config::TufTargetNames; +use core::str; +use std::fs::OpenOptions; +use std::io::Read; +use std::path::{Path, PathBuf}; +use tuf_no_std::remote::TransportError; +use tuf_no_std::utils::MemoryStorage; +use tuf_no_std::TufTransport; +use url::Url; + +#[derive(Debug)] +pub struct SigstoreKeys { + pub rekor_key: Vec, + pub fulcio_cert: Vec, + pub ctlog_key: Vec, +} + +pub async fn load_tuf_filesystem( + root: &Path, + metadata_base_path: &Url, + _targets_base_path: &Url, + target_names: &TufTargetNames, +) -> Result { + let root = std::fs::read(root).expect("failed to read a root file at the providedd path"); + let mut storage = MemoryStorage { + root: tuf_no_std::heapless::Vec::from_slice(&root).expect("root file > 2048 bytes"), + snapshot: None, + targets: None, + timestamp: None, + uncommitted_root: None, + }; + let repository_path = metadata_base_path + .to_file_path() + .expect("did not provide a file path"); + let mut transport = FilesystemTransport { repository_path }; + let update_start = chrono::Utc::now().signed_duration_since(chrono::DateTime::UNIX_EPOCH); + let update_start = update_start + .to_std() + .expect("failed to convert update start to std data type"); + let update_start = tuf_no_std::UtcTime::from_unix_duration(update_start) + .expect("could not create update start timestamp"); + tuf_no_std::update_repo(&mut storage, &mut transport, 100, update_start) + .map_err(Bt2XError::TufError)?; + + let mut rekor_buf = [0u8; 1024]; + let mut fulcio_buf = [0u8; 1024]; + let mut ctlog_buf = [0u8; 1024]; + let rekor_key = tuf_no_std::fetch_and_verify_target_file( + &mut storage, + &mut transport, + target_names.rekor.as_bytes(), + &mut rekor_buf, + ) + .map_err(Bt2XError::TufError)?; + let fulcio_crt = tuf_no_std::fetch_and_verify_target_file( + &mut storage, + &mut transport, + target_names.fulcio.as_bytes(), + &mut fulcio_buf, + ) + .map_err(Bt2XError::TufError)?; + let ctlog_key = tuf_no_std::fetch_and_verify_target_file( + &mut storage, + &mut transport, + target_names.ctlog.as_bytes(), + &mut ctlog_buf, + ) + .map_err(Bt2XError::TufError)?; + + Ok(SigstoreKeys { + ctlog_key: ctlog_key.to_vec(), + fulcio_cert: fulcio_crt.to_vec(), + rekor_key: rekor_key.to_vec(), + }) +} + +#[derive(Debug)] +pub struct FilesystemTransport { + repository_path: PathBuf, +} + +impl FilesystemTransport { + fn fetch_impl<'o>( + &self, + path: &PathBuf, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::remote::TransportError> { + OpenOptions::new() + .read(true) + .open(path) + .map_err(|_| TransportError::FetchError) + .and_then(|mut f| f.read(out).map_err(|_| TransportError::FetchError)) + .map(|n| &out[..n]) + } +} + +impl TufTransport for FilesystemTransport { + fn fetch_root<'o>( + &self, + version: std::num::NonZeroU64, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::remote::TransportError> { + self.fetch_impl( + &self.repository_path.join(format!("{version}.root.der")), + out, + ) + } + + fn fetch_timestamp<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::remote::TransportError> { + self.fetch_impl(&self.repository_path.join("1.timestamp.der"), out) + } + + fn fetch_snapshot<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::remote::TransportError> { + self.fetch_impl(&self.repository_path.join("1.snapshot.der"), out) + } + + fn fetch_targets<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::remote::TransportError> { + self.fetch_impl(&self.repository_path.join("1.targets.der"), out) + } + + fn fetch_target_file<'o>( + &self, + metapath: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::remote::TransportError> { + let metapath = PathBuf::from(str::from_utf8(metapath).unwrap()); + if metapath.is_absolute() { + panic!("provided absolute path") + } + self.fetch_impl(&self.repository_path.join("targets").join(metapath), out) + } +} diff --git a/bt2x/bt2x-common/src/verifier/bt.rs b/bt2x/bt2x-common/src/verifier/bt.rs new file mode 100644 index 0000000..c98a4df --- /dev/null +++ b/bt2x/bt2x-common/src/verifier/bt.rs @@ -0,0 +1,370 @@ +#[cfg(feature = "oci")] +use crate::artifact::source::oci::OciSource; +use crate::error::Bt2XError; +use anyhow::{anyhow, Context}; +use async_trait::async_trait; +use oci_distribution::secrets::RegistryAuth; +use sigstore::cosign::verification_constraint::{ + VerificationConstraint, VerificationConstraintVec, +}; +use sigstore::cosign::{ + verify_constraints, Client as CosignClient, CosignCapabilities, SignatureLayer, +}; +use sigstore::registry::OciReference; +use std::fmt::Debug; +use tracing::debug; +use url::Url; + +use crate::merkle::{MerkleProofVerifier, Rfc6269Default, Rfc6269HasherTrait}; +use crate::verifier::{Artifact, Verifier}; + +use typed_builder::TypedBuilder as Builder; + +use sigstore::crypto::{CosignVerificationKey, SigningScheme}; +use sigstore::registry::Auth; + +use anyhow::Result; + +use digest::Output; + +use crate::artifact::source::ArtifactSource; +use crate::gossip::send_checkpoint; +use crate::rekor::RekorClient; +use crate::verifier::Artifact::{Binary, BundledBinary}; +use olpc_cjson::CanonicalFormatter; +use serde::Serialize; +use sigstore::cosign::bundle::{PayloadBody, SignedArtifactBundle}; +use sigstore::rekor::models::log_entry::{Body, InclusionProof}; +use sigstore::rekor::models::{ConsistencyProof, LogEntry}; + +/// Runs verification based on the given Sigstore keys. +#[derive(Builder)] +pub struct BinaryTransparencyVerifier { + rekor_client: RekorClient, + cosign_client: CosignClient, + keys: SigstoreKeys, + monitors: Vec, +} + +#[derive(Builder, Debug)] +pub struct SigstoreKeys { + rekor_pub_key: String, + #[builder(default, setter(strip_option))] + fulcio_cert: Option, + #[builder(default, setter(strip_option))] + ct_log_key: Option, +} + +#[async_trait(? Send)] +#[cfg(feature = "oci")] +impl Verifier, OciSource, OciReference, VerificationConstraintVec> + for BinaryTransparencyVerifier +{ + async fn verify( + &mut self, + source: &mut OciSource, + identifier: &OciReference, + constraints: &VerificationConstraintVec, + ) -> Result>> { + let target = identifier; + debug!("attempting to verify: {target}"); + let auth = match &source.auth { + RegistryAuth::Anonymous => Auth::Anonymous, + RegistryAuth::Basic(s1, s2) => Auth::Basic(s1.clone(), s2.clone()), + }; + let (cosign_image, source_digest) = + match self.cosign_client.triangulate(target, &auth).await { + Ok((cosign_image, source_digest)) => (cosign_image, source_digest), + Err(err) => return Err(anyhow!("failed to get cosign image for {target} ({err})")), + }; + let source_image_with_digest = OciReference::with_digest( + target.registry().to_string(), + target.repository().to_string(), + source_digest.to_string(), + ); + + debug!("source image is: {source_image_with_digest}"); + debug!("cosign image is: {cosign_image}"); + + let signature_layers = match self + .cosign_client + .trusted_signature_layers(&auth, &source_digest, &cosign_image) + .await + { + Ok(layers) => layers, + Err(err) => return Err(anyhow!("Failed to get signature layers {err}")), + }; + + verify_constraints(&signature_layers, constraints.iter()).map_err(|err| { + debug!("verifying contraints failed {err}"); + anyhow!("verification of constraint(s) failed {err}") + })?; + debug!("pulling artifact"); + let rekor_bundle = if let [SignatureLayer { + bundle: Some(bundle), + .. + }] = &signature_layers[..] + { + Ok(bundle) + } else { + Err(anyhow!( + "failed to extract bundle and signature from signature layerslayers" + )) + }?; + + debug!("successfully passed all checks providing by signature layers ..."); + // needs log id of first entry + let online_entry = self + .rekor_client + .get_log_entry(rekor_bundle.payload.log_index as u64) + .await + .map_err(|err| anyhow!("failed to get online entry {err}"))?; + + // Gossip entry to monitor + let checkpoint = online_entry + .verification + .inclusion_proof + .as_ref() + .ok_or(anyhow!("missing inclusion proof")) + .and_then(|ip| ip.checkpoint.parse().context("failed to parse checkpoint"))?; + + for url in &self.monitors { + send_checkpoint(url, &checkpoint).await.map_err(|err| { + anyhow!("failed to get consistency confirmation from monitor {err:?}") + })?; + } + + let constraints: VerificationConstraintVec = + vec![Box::new(InclusionVerifier { online_entry })]; + verify_constraints(&signature_layers, constraints.iter()) + .map_err(|err| anyhow!("online verification failed {err}"))?; + debug!( + "online check succeeded for entry with id: {}", + rekor_bundle.payload.log_index + ); + let image_data = source.get_artifact(&source_image_with_digest).await?; + let cosign_bundle = match source.get_bundle(&source_image_with_digest).await { + Ok(bundle) => bundle, + Err(_) => { + return Ok(Binary(image_data)); + } + }; + let rekor_key = CosignVerificationKey::from_pem( + self.keys.rekor_pub_key.as_bytes(), + &SigningScheme::default(), + ) + .map_err(|err| anyhow!("Failed to parse Rekor key {err}"))?; + + let signed_bundle = SignedArtifactBundle::new_verified( + std::str::from_utf8(&cosign_bundle).context("bundle had invalid UTF-8")?, + &rekor_key, + ) + .map_err(|err| anyhow!("failed to produce signed artifact bundle {err}"))?; + + let entry = self + .rekor_client + .get_log_entry(signed_bundle.rekor_bundle.payload.log_index as u64) + .await + .map_err(|err| anyhow!("failed to get online entry {err}"))?; + + // Verify inclusion + let inclusion_proof = entry + .verification + .inclusion_proof + .context("online entry did contain inclusion proof")?; + verify_inclusion_bundle(&signed_bundle.rekor_bundle.payload.body, &inclusion_proof) + .map_err(|_| anyhow!("inclusion proof failed"))?; + + // Gossip entry to monitor + let checkpoint = inclusion_proof + .checkpoint + .parse() + .context("failed to parse checkpoint")?; + + for url in &self.monitors { + send_checkpoint(url, &checkpoint).await.map_err(|err| { + anyhow!("failed to get consistency confirmation from monitor {err:?}") + })?; + } + + self.cosign_client + .verify_blob_with_bundle(image_data.as_slice(), &signed_bundle.rekor_bundle) + .map_err(|err| anyhow!("failed to verify blob with bundle {err:?}"))?; + + Ok(BundledBinary { + binary: image_data, + bundle: cosign_bundle, + }) + } +} + +#[derive(Debug)] +pub struct InclusionVerifier { + pub(crate) online_entry: LogEntry, +} + +impl VerificationConstraint for InclusionVerifier { + fn verify(&self, signature_layer: &SignatureLayer) -> sigstore::errors::Result { + let Some(inclusion_proof) = self.online_entry.verification.inclusion_proof.as_ref() else { + return Ok(false); + }; + use sigstore::cosign::bundle::{Bundle, Payload}; + if let SignatureLayer { + bundle: + Some(Bundle { + payload: Payload { body, .. }, + .. + }), + .. + } = signature_layer + { + match verify_inclusion_bundle(body, inclusion_proof) { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } + } else { + Ok(false) + } + } +} + +pub fn verify_inclusion_rekor_entry( + body: &Body, + inclusion_proof: &InclusionProof, +) -> Result<(), Bt2XError> { + let mut body_serialized = Vec::new(); + let mut ser = + serde_json::Serializer::with_formatter(&mut body_serialized, CanonicalFormatter::new()); + body.serialize(&mut ser).unwrap(); + verify_inclusion_raw(&body_serialized, inclusion_proof) +} + +pub fn verify_inclusion_bundle( + body: &PayloadBody, + inclusion_proof: &InclusionProof, +) -> Result<(), Bt2XError> { + let mut body_serialized = Vec::new(); + let mut ser = + serde_json::Serializer::with_formatter(&mut body_serialized, CanonicalFormatter::new()); + body.serialize(&mut ser).unwrap(); + verify_inclusion_raw(&body_serialized, inclusion_proof) +} + +pub(crate) fn verify_inclusion_raw( + body: &[u8], + inclusion_proof: &InclusionProof, +) -> Result<(), Bt2XError> { + let leaf_hash = Rfc6269Default::hash_leaf(body); + let proof_hashes: Vec> = inclusion_proof + .hashes + .iter() + .map(|h| { + hex::decode(h) + .map_err(Bt2XError::HexDecodingError) + .and_then(|h| { + <[u8; 32]>::try_from(h.as_slice()).map_err(|_| Bt2XError::InvalidHashLength) + }) + .map(Output::::from) + }) + .collect::, Bt2XError>>()?; + + let root_hash = hex::decode(&inclusion_proof.root_hash) + .map_err(Bt2XError::HexDecodingError) + .and_then(|h| <[u8; 32]>::try_from(h.as_slice()).map_err(|_| Bt2XError::InvalidHashLength)) + .map(Output::::from)?; + + Rfc6269Default::verify_inclusion( + inclusion_proof.log_index as usize, + &leaf_hash, + inclusion_proof.tree_size as usize, + &proof_hashes, + &root_hash, + ) + .map_err(Bt2XError::InclusionProofFailed)?; + Ok(()) +} + +pub fn verify_consistency( + old_size: usize, + new_size: usize, + proof: &ConsistencyProof, + old_root: &[u8; 32], + new_root: &[u8; 32], +) -> Result<(), Bt2XError> { + let proof_hashes: Vec<_> = proof + .hashes + .iter() + .map(|s| { + hex::decode(s) + .map_err(Bt2XError::HexDecodingError) + .and_then(|v| { + <[u8; 32]>::try_from(v.as_slice()).map_err(|_| Bt2XError::InvalidHashLength) + }) + .map(Output::::from) + }) + .collect::, Bt2XError>>()?; + + Rfc6269Default::verify_consistency( + old_size, + new_size, + &proof_hashes, + old_root.into(), + new_root.into(), + ) + .map_err(Bt2XError::ConsistencyProofFailed) +} + +#[cfg(feature = "bt")] +pub mod constraints { + use sigstore::cosign::verification_constraint::VerificationConstraint; + use sigstore::cosign::SignatureLayer; + + #[derive(Debug)] + pub struct KeylessSigningEnforcer {} + + impl VerificationConstraint for KeylessSigningEnforcer { + fn verify(&self, signature_layer: &SignatureLayer) -> sigstore::errors::Result { + Ok(signature_layer.certificate_signature.is_some()) + } + } +} + +#[cfg(test)] +mod test { + use crate::verifier::bt::verify_inclusion_rekor_entry; + + #[tokio::test] + async fn test_verify_inclusion() { + let rekor_config = sigstore::rekor::apis::configuration::Configuration::default(); + let entry = + sigstore::rekor::apis::entries_api::get_log_entry_by_index(&rekor_config, 16056995) + .await + .expect("could not fetch entry"); + + assert!(verify_inclusion_rekor_entry( + &entry.body, + &entry.verification.inclusion_proof.unwrap() + ) + .is_ok()); + } + + #[tokio::test] + async fn test_verify_inclusion_fail() { + let rekor_config = sigstore::rekor::apis::configuration::Configuration::default(); + let mut entry = + sigstore::rekor::apis::entries_api::get_log_entry_by_index(&rekor_config, 16056995) + .await + .expect("could not fetch entry"); + entry + .verification + .inclusion_proof + .as_mut() + .unwrap() + .log_index += 1; + assert!(verify_inclusion_rekor_entry( + &entry.body, + &entry.verification.inclusion_proof.unwrap() + ) + .is_err()); + } +} diff --git a/bt2x/bt2x-common/src/verifier/mod.rs b/bt2x/bt2x-common/src/verifier/mod.rs new file mode 100644 index 0000000..6025600 --- /dev/null +++ b/bt2x/bt2x-common/src/verifier/mod.rs @@ -0,0 +1,30 @@ +use crate::artifact::source::ArtifactSource; +use anyhow::Result; +use async_trait::async_trait; + +/// Abstraction trait of a verifier. +#[async_trait(? Send)] // this is to avoid issues with Box, not sure if this the correct way to do this +pub trait Verifier { + /// Verify the the artifact specified by the identifier fetched from the source under the given constraints. + /// Returns a verified [[Artifact]]. + async fn verify( + &mut self, + source: &mut S, + identifier: &I, + constraints: &C, + ) -> Result> + where + S: ArtifactSource; +} + +/// There are different kinds of artifacts that can be verified. +#[derive(Debug)] +pub enum Artifact { + /// Raw binary without a bundled signature. + Binary(T), + /// Binary that is bundled with a signature. + BundledBinary { binary: T, bundle: T }, +} + +#[cfg(feature = "bt")] +pub mod bt; diff --git a/bt2x/bt2x-embedded/.gitignore b/bt2x/bt2x-embedded/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/bt2x/bt2x-embedded/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/bt2x/bt2x-embedded/Cargo.toml b/bt2x/bt2x-embedded/Cargo.toml new file mode 100644 index 0000000..449a1fc --- /dev/null +++ b/bt2x/bt2x-embedded/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "bt2x-embedded" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +# crate-type = ["dylib", "staticlib"] + +[features] +default = ["no_std"] +no_std = [] + + +[dependencies] +serde = { workspace = true, features = ["derive"] } +# serde_with = { path = "../vendor/serde_with/serde_with", version = "2.2", default-features = false, features = [ +# "hex", +# "base64", +# "macros", +# "alloc", +# ] } +#heapless = "0.7" +serde-json-core = { workspace = true } +base64 = { workspace = true, features = [] } +hex = { workspace = true, features = ["serde"] } + +sha2 = { workspace = true, features = [] } +der = { workspace = true, features = [] } +signature = { workspace = true, features = [] } +ecdsa = { workspace = true, features = ["der", "verifying", "arithmetic", "pkcs8"] } +spki = { workspace = true, features = [] } +pkcs8 = { workspace = true, features = [] } +p256 = { workspace = true, features = ["ecdsa", "ecdsa-core", "pem"] } +x509-cert = { workspace = true, features = [] } +pem-rfc7468 = { workspace = true, features = [] } + +base64ct = { workspace = true, features = [] } +[dev-dependencies] +serde_json = { workspace = true, features = ["alloc"] } +postcard = { workspace = true, features = ["alloc"] } +dhat = { workspace = true } diff --git a/bt2x/bt2x-embedded/src/bundle/mod.rs b/bt2x/bt2x-embedded/src/bundle/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/bt2x/bt2x-embedded/src/lib.rs b/bt2x/bt2x-embedded/src/lib.rs new file mode 100644 index 0000000..ddb3b45 --- /dev/null +++ b/bt2x/bt2x-embedded/src/lib.rs @@ -0,0 +1,627 @@ +#![no_std] +//! A `no-std` implementation to run verification of Sigstore/Cosign style signatures. +//! +//! ## Signature Format +//! +//! At a top level the signature has the following modification to the [Cosign format](https://github.com/sigstore/cosign/blob/8defb0e72baa6c0385f4097723a3574e6d0406d0/specs/SIGNATURE_SPEC.md). +//! ```json +//! { +//! "SignedEntryTimestamp": "", +//! "Payload": "" +//! } +//! ``` +//! +//! This has the following benefits: +//! - We can separate the parsing into multiple steps, +//! and do not have to deserialize and re-serialize multiple times to verify signatures. +//! - Decoding the B64 strings gives us the strings we need to verify signatures on, we can do so and then parse it. +//! +//! +//! The [BundlePayload] follows the regular format, meaning we only use a very minor modification to the original specification. +//! ## Example +//! +//! This example verifies: +//! - that the binary was correctly signed with a Fulcio signing certificate, +//! - it was entered into the Rekor log within 10 minutes, +//! - the signing certificate was issued to `foo@example.org` based on being authenticated by the OIDC provider `issuer.example.org`. +//! +//! ```ignore +//! let signature = include_bytes!("bundle.json"); +//! let binary = include_bytes!("example.bin"); +//! let rekor_pub_key = include_bytes!("rekor_pub.pem"); +//! let fulcio_crt = include_bytes!("fulcio_crt.pem"); +//! +//! bt2x_embedded::verify_bt( +//! rekor_pub_key, +//! fulcio_crt, +//! signature, +//! binary, +//! &[("foo@example.org", "issuer.example.org")], +//! ).expect("failed to verify binary"); +//! ``` + +// #[cfg(test)] +// #[macro_use] +// extern crate std; +// extern crate alloc; + +// use embedded_alloc::Heap; +// +// #[global_allocator] +// static HEAP: Heap = Heap::empty(); + +//pub mod models; + +const MAX_CERT_SIZE: usize = 4096; +const MAX_SIGNATURE_SIZE: usize = 128; +const MAX_PAYLOAD_SIZE: usize = 4096; +const MAX_PUBKEY_SIZE: usize = 1024; + +/// 'Compact' version of a cosign bundle, which is optimized to eliminate the need for a JSON encoder. +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct Bundle<'a> { + /// Base64 encoded SET. + pub signed_entry_timestamp: &'a str, + /// Base64 encoded [BundlePayload]. + pub payload: &'a str, +} + +/// Cosign Bundle Payload. +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BundlePayload<'a> { + /// Base64 encoded [BundleBody]. + pub body: &'a str, + /// The timestamp of when the bundle was presented to the log. Has to be within ten minutes of the creation of a Fulcio signing cert. + pub integrated_time: u64, + /// The index of the entry in the log. + pub log_index: u32, + /// The ID of the log entry. + #[serde(rename = "logID")] + pub log_id: &'a str, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +pub struct BundleBody<'a> { + pub kind: Kind, + pub api_version: &'a str, + pub spec: HashedRekordObj<'a>, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "'de: 'a"))] +pub struct HashedRekordObj<'a> { + pub data: HashRekordData<'a>, + pub signature: Signature<'a>, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(bound(deserialize = "'de: 'a"))] +pub struct HashRekordData<'a> { + pub hash: Hash<'a>, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Hash<'a> { + pub algorithm: HashAlgorithm, + pub value: &'a str, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum HashAlgorithm { + Sha256, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Signature<'a> { + pub format: Option, + pub content: &'a str, + pub public_key: PublicKey<'a>, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKey<'a> { + pub content: &'a str, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum SignatureFormat { + //Pgp, + //Minisign, + X509, + //Ssh, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Kind { + HashedRekord, +} + +pub struct FuncResult<'a> { + pub integrated_time: u32, + pub log_index: u32, + pub log_id: &'a str, + pub body: BundleBody<'a>, +} + +pub type EcdsaP256Sha256Asn1Signature = p256::ecdsa::Signature; +pub type EcdsaP256Key = p256::ecdsa::VerifyingKey; + +use core::fmt::Debug; +use p256::ecdsa::signature::DigestVerifier; +pub use serde_json_core::de::Error as SerdeDeserializationError; +pub use sha2::{Digest, Sha256}; + +#[derive(Debug)] +pub enum VerificationError { + /// Failed to deserialize the [Bundle] + DeserializationFailedBundle(serde_json_core::de::Error), + /// Failed to deserialize the [BundlePayload]. + DeserializationFailedBundlePayload, + /// Failed to deserialize the [BundleBody]. + DeserializationFailedBundleBody, + SetVerificationFailed, + ParsingPubKeyFailed, + ParsingSignatureFailed, + DecodingB64Failed, + DecodingHexFailed, + /// Hash in the bundle does not matched the calculated hash. + HashDifferent, + /// The signing cert is not trusted. + UntrustedCert, + /// Failed to PEM decode a certificate. + ParsingCertFailedPem(pem_rfc7468::Error), + /// Failed to DER decode a certificate. + ParsingCertFailedDer(der::Error), + /// Log timestamp is not within the validity period. + TimestampNotInValidity, + /// Pubkey type is not supported. + UnsupportedPubKey, + InvalidSignatureSet, + InvalidSignatureCertificate, + InvalidSignature, + ErrorThatIndicatesImplementationError, + OutOfBufferSpace, + /// Signing certificate identity is does not match a trusted identity. + SubjectIdentityMismatch, +} + +#[derive(Debug)] +pub enum SignedObject<'a, H: Digest + Debug> { + Blob(&'a [u8]), + Digest(H), +} + +impl<'a, H: Digest + Debug> From<&'a [u8]> for SignedObject<'a, H> { + fn from(value: &'a [u8]) -> Self { + SignedObject::Blob(value) + } +} + +impl<'a> From for SignedObject<'a, Sha256> { + fn from(value: Sha256) -> Self { + SignedObject::Digest(value) + } +} + +fn parse_cert<'a>( + pem: &[u8], + output: &'a mut [u8], +) -> Result, VerificationError> { + let (_, decoded) = + pem_rfc7468::decode(pem, output).map_err(VerificationError::ParsingCertFailedPem)?; + + ::from_der(decoded).map_err(VerificationError::ParsingCertFailedDer) +} + +/// Verify that PEM encoded signing certificate was signed by the root CA. +fn verify_signing_cert<'a>( + root_cert: &[u8], + pem_signing: &[u8], + output_buf: &'a mut [u8], +) -> Result, VerificationError> { + // Load root cert. + let mut root_cert_buf = [0_u8; MAX_CERT_SIZE]; + let root_cert = parse_cert(root_cert, root_cert_buf.as_mut_slice())?; + + // Load signing cert. + let signing_cert = parse_cert(pem_signing, output_buf)?; + let mut tbs_encoding_buf = [0_u8; MAX_CERT_SIZE]; + + // Encode the signing cert as a TBS, which is the part of the certificate that is signed. + let tbs_encoded = signing_cert + .tbs_certificate + .encode_to_slice(tbs_encoding_buf.as_mut_slice()) + .map_err(|_| VerificationError::ErrorThatIndicatesImplementationError)?; + + // Load the CA key. + let spki = root_cert.tbs_certificate.subject_public_key_info; + let pubkey = + ecdsa::VerifyingKey::try_from(spki).map_err(|_| VerificationError::ParsingPubKeyFailed)?; + + // Verify that the certificate was signed by the CA. + verify_signature( + signing_cert.signature.raw_bytes(), + tbs_encoded.into(), + &pubkey, + ) + .map_err(|_| VerificationError::InvalidSignatureCertificate)?; + + Ok(signing_cert) +} + +fn verify_signature( + signature: &[u8], + msg: SignedObject<'_, Sha256>, + verifying_key: &VerifyingKey, +) -> Result<(), VerificationError> { + // create a Sha256 object + let hasher = match msg { + SignedObject::Blob(blob) => ::new().chain_update(blob), + SignedObject::Digest(hasher) => hasher, + }; + + let signature = ecdsa::Signature::from_der(signature) + .map_err(|_| VerificationError::ParsingSignatureFailed)?; + verifying_key + .verify_digest(hasher, &signature) + .map(|_| ()) + .map_err(|_| VerificationError::InvalidSignature) +} + +/// Verifies the binary in `msg`, which can be either a &[u8] or calculated `Sha256` hash. +/// The `rekor_pub_key` is PEM encoded key of the Rekor server that signed the entry timestamp. +/// The parameter `root_cert` is the PEM encoded root certificate of the Fulcio CA that issues the signing certificates. +/// The signature is provided via the `bundle` parameter, it has to be formatted in a modified cosign bundle format, refer to the [Bundle struct](Bundle) for more information. +pub fn verify_bt<'a, 'b>( + rekor_pub_key: &[u8], + root_cert: &[u8], + bundle: &'a [u8], + msg: impl Into>, + subject_identities: &[(&str, &str)], +) -> Result, VerificationError> { + // decode the bundle + let (bundle, _) = serde_json_core::from_slice::(bundle) + .map_err(VerificationError::DeserializationFailedBundle)?; + + // Decode the SET from Base64 to the raw signature. + let mut set_buf = [0_u8; MAX_SIGNATURE_SIZE]; + let set_decoded = Base64::decode( + bundle.signed_entry_timestamp.as_bytes(), + set_buf.as_mut_slice(), + ) + .map_err(|_| VerificationError::ErrorThatIndicatesImplementationError)?; + + // Decode the payload of the bundle from Base64 to JSOn. + let mut payload_buf = [0_u8; MAX_PAYLOAD_SIZE]; + let payload_decoded = Base64::decode(bundle.payload.as_bytes(), payload_buf.as_mut_slice()) + .map_err(|_| VerificationError::DecodingB64Failed)?; + + // Decode the Rekor public key. + let mut pem_decode_buf = [0_u8; MAX_PUBKEY_SIZE]; + let (_, decoded) = pem_rfc7468::decode(rekor_pub_key, pem_decode_buf.as_mut_slice()) + .map_err(|_| VerificationError::ParsingPubKeyFailed)?; + let spki = spki::SubjectPublicKeyInfoRef::from_der(decoded) + .map_err(|_| VerificationError::ParsingPubKeyFailed)?; + let rekor_pub_key = + ecdsa::VerifyingKey::try_from(spki).map_err(|_| VerificationError::ParsingPubKeyFailed)?; + + // Verify that the signature was presented to the Rekor log by verifying that the payload was signed by the log with its public key. + verify_signature(set_decoded, payload_decoded.into(), &rekor_pub_key) + .map_err(|_| InvalidSignatureSet)?; + + // Decode the payload JSON. + let (payload, _) = serde_json_core::from_slice::(payload_decoded) + .map_err(|_| VerificationError::DeserializationFailedBundlePayload)?; + + // Extract the Base64 encoded body of the bundle. + let mut body_buf = [0_u8; 4096]; + let body_decoded = Base64::decode(payload.body.as_bytes(), body_buf.as_mut_slice()) + .map_err(|_| VerificationError::DecodingB64Failed)?; + + // Decode the JSON bundle body. + let (body, _) = serde_json_core::from_slice::(body_decoded) + .map_err(|_| VerificationError::DeserializationFailedBundleBody)?; + + // Decode hash from Rekor Entry + let mut given_digest = [0_u8; 32]; + hex::decode_to_slice(body.spec.data.hash.value, &mut given_digest) + .map_err(|_| VerificationError::DecodingHexFailed)?; + + // Base64 decode the signing cert. + let mut cert_buf = [0_u8; MAX_CERT_SIZE]; + let cert_decoded = Base64::decode( + body.spec.signature.public_key.content.as_bytes(), + cert_buf.as_mut_slice(), + ) + .map_err(|_| VerificationError::DecodingB64Failed)?; + + // Load the certificate and verify that it was issued by the trusted Fulcio instance. + let mut cert_pem_decode_buf = [0_u8; MAX_CERT_SIZE]; + let signing_cert = + verify_signing_cert(root_cert, cert_decoded, cert_pem_decode_buf.as_mut_slice())?; + if signing_cert + .tbs_certificate + .validity + .not_after + .to_unix_duration() + .as_secs() + < payload.integrated_time + || signing_cert + .tbs_certificate + .validity + .not_before + .to_unix_duration() + .as_secs() + > payload.integrated_time + { + return Err(VerificationError::TimestampNotInValidity); + } + // Verify that the subject that requested the signing certificate is trusted. + verify_subject_identities(&signing_cert, subject_identities) + .map_err(|_| VerificationError::SubjectIdentityMismatch)?; + + // Extract the public key from the signing certificate. + let spki = signing_cert.tbs_certificate.subject_public_key_info; + let pubkey = + ecdsa::VerifyingKey::try_from(spki).map_err(|_| VerificationError::ParsingPubKeyFailed)?; + + // Decode the signature. + let mut signature_buf = [0_u8; MAX_SIGNATURE_SIZE]; + let signature_decoded = Base64::decode( + body.spec.signature.content.as_bytes(), + signature_buf.as_mut_slice(), + ) + .map_err(|_| VerificationError::DecodingB64Failed)?; + + // Verify the signature of the binary. + verify_signature(signature_decoded, msg.into(), &pubkey) + .map_err(|_| VerificationError::InvalidSignature)?; + Ok(bundle) +} + +use base64ct::{Base64, Encoding}; +use der::asn1::{BitStringRef, IntRef, ObjectIdentifier, OctetStringRef, SequenceOf, SetOf}; +//use base64::{Engine as _, alphabet, engine::{self, general_purpose}}; +use der::{ + AnyRef, Decode, DecodeValue, Encode, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, + Sequence, Tag, ValueOrd, Writer, +}; + +use p256::ecdsa; +use p256::ecdsa::VerifyingKey; +//use p256::ecdsa::VerifyingKey; +use crate::VerificationError::InvalidSignatureSet; +use pkcs8::SubjectPublicKeyInfoRef; +use spki::AlgorithmIdentifierRef; +use x509_cert::time::Validity; +use x509_cert::Version; +//use spki::DecodePublicKey; + +#[derive(Clone, Debug, Sequence, ValueOrd)] +struct CertificateRef<'a> { + pub tbs_certificate: TbsCertificateRef<'a>, + pub signature_algorithm: AlgorithmIdentifierRef<'a>, + pub signature: BitStringRef<'a>, +} + +const N_TBS_EXT: usize = 10; +const N_TBS_RDN_SEQ: usize = 10; +const N_TBS_RDN: usize = 5; + +#[derive(Clone, Debug, Sequence, ValueOrd)] +#[allow(missing_docs)] +struct TbsCertificateRef<'a> { + /// The certificate version + /// + /// Note that this value defaults to Version 1 per the RFC. However, + /// fields such as `issuer_unique_id`, `subject_unique_id` and `extensions` + /// require later versions. Care should be taken in order to ensure + /// standards compliance. + #[asn1(context_specific = "0", default = "Default::default")] + pub version: Version, + + pub serial_number: SerialNumberRef<'a>, + pub signature: AlgorithmIdentifierRef<'a>, + pub issuer: NameRef<'a, N_TBS_RDN, N_TBS_RDN_SEQ>, + pub validity: Validity, + pub subject: NameRef<'a, N_TBS_RDN, N_TBS_RDN_SEQ>, + pub subject_public_key_info: SubjectPublicKeyInfoRef<'a>, + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] + pub issuer_unique_id: Option>, + + #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] + pub subject_unique_id: Option>, + + #[asn1(context_specific = "3", tag_mode = "EXPLICIT", optional = "true")] + pub extensions: Option>, +} + +type NameRef<'a, const N_RDN: usize, const N_RDN_SEQ: usize> = RdnSequence<'a, N_RDN, N_RDN_SEQ>; + +#[derive(Clone, Debug, Default)] +struct RdnSequence<'a, const N_RDN: usize, const N_RDN_SEQ: usize>( + pub SequenceOf, N_RDN_SEQ>, +); +impl_newtype_generics!( + RdnSequence<'a, N_IMPL1, N_IMPL2>, + SequenceOf, N_IMPL2>, + N_IMPL1, + N_IMPL2 +); + +#[derive(Clone, Debug, Default)] +struct RelativeDistinguishedName<'a, const N: usize>(pub SetOf, N>); +impl_newtype_generics!( + RelativeDistinguishedName<'a, N_IMPL1>, + SetOf, N_IMPL1>, + N_IMPL1 +); + +type Extensions<'a, const N: usize> = SequenceOf, N>; + +#[derive(Clone, Debug, Sequence, ValueOrd)] +pub struct ExtensionRef<'a> { + pub extn_id: ObjectIdentifier, + + #[asn1(default = "bool::default")] + pub critical: bool, + + pub extn_value: OctetStringRef<'a>, +} + +#[derive(Clone, Debug, Sequence, ValueOrd)] +#[allow(missing_docs)] +struct AttributeTypeAndValue<'a> { + pub oid: AttributeType, + pub value: AttributeValue<'a>, +} + +type AttributeType = ObjectIdentifier; + +type AttributeValue<'a> = AnyRef<'a>; + +#[derive(Clone, Debug, ValueOrd)] +struct SerialNumberRef<'a> { + inner: IntRef<'a>, +} + +impl<'a> SerialNumberRef<'a> { + const MAX_DECODE_LEN: Length = Length::new(21); +} + +impl<'a> EncodeValue for SerialNumberRef<'a> { + fn value_len(&self) -> DerResult { + self.inner.value_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> DerResult<()> { + self.inner.encode_value(writer) + } +} + +impl<'a> DecodeValue<'a> for SerialNumberRef<'a> { + fn decode_value>(reader: &mut R, header: Header) -> DerResult { + let inner = IntRef::<'a>::decode_value(reader, header)?; + + if inner.len() > SerialNumberRef::MAX_DECODE_LEN { + return Err(ErrorKind::Overlength.into()); + } + + Ok(Self { inner }) + } +} + +impl<'a> FixedTag for SerialNumberRef<'a> { + const TAG: Tag = as FixedTag>::TAG; +} + +type DerResult = Result; + +fn verify_subject_identities( + cert: &CertificateRef<'_>, + subject_identities: &[(&str, &str)], +) -> Result<(), ()> { + let extensions = cert.tbs_certificate.extensions.as_ref().ok_or(())?; + let issuer_ext = extensions + .iter() + .find(|&ext| ext.extn_id == ISSUER_OID) + .ok_or(())?; + let subject_ext = extensions + .iter() + .find(|&ext| ext.extn_id == SUBJECT_ALT_NAME_OID) + .ok_or(())?; + subject_identities + .iter() + .find(|(subject, issuer)| { + let issuer = OctetStringRef::new(issuer.as_bytes()).expect("failed to parse"); + // first 4 bytes contain encoding specific data, skip them + issuer == issuer_ext.extn_value + && &subject_ext.extn_value.as_bytes()[4..] == subject.as_bytes() + }) + .ok_or(())?; + Ok(()) +} + +pub const ISSUER_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.1"); +pub const SUBJECT_ALT_NAME_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.29.17"); +#[macro_export] +macro_rules! impl_newtype_generics { + ($newtype:ty, $inner:ty, $( $i:ident ),+) => { + #[allow(unused_lifetimes)] + impl<'a, $( const $i: usize ),*> From<$inner> for $newtype { + #[inline] + fn from(value: $inner) -> Self { + Self(value) + } + } + + #[allow(unused_lifetimes)] + impl<'a, $( const $i: usize ),*> From<$newtype> for $inner { + #[inline] + fn from(value: $newtype) -> Self { + value.0 + } + } + + #[allow(unused_lifetimes)] + impl<'a, $( const $i: usize ),*> AsRef<$inner> for $newtype { + #[inline] + fn as_ref(&self) -> &$inner { + &self.0 + } + } + + #[allow(unused_lifetimes)] + impl<'a, $( const $i: usize ),*> AsMut<$inner> for $newtype { + #[inline] + fn as_mut(&mut self) -> &mut $inner { + &mut self.0 + } + } + + #[allow(unused_lifetimes)] + impl<'a, $( const $i: usize ),*> ::der::FixedTag for $newtype { + const TAG: ::der::Tag = <$inner as ::der::FixedTag>::TAG; + } + + impl<'a, $( const $i: usize ),*> ::der::DecodeValue<'a> for $newtype { + fn decode_value>( + decoder: &mut R, + header: ::der::Header, + ) -> ::der::Result { + Ok(Self(<$inner as ::der::DecodeValue>::decode_value( + decoder, header, + )?)) + } + } + + #[allow(unused_lifetimes)] + impl<'a, $( const $i: usize ),*> ::der::EncodeValue for $newtype { + fn encode_value(&self, encoder: &mut impl ::der::Writer) -> ::der::Result<()> { + self.0.encode_value(encoder) + } + + fn value_len(&self) -> ::der::Result<::der::Length> { + self.0.value_len() + } + } + + #[allow(unused_lifetimes)] + impl<'a, $( const $i: usize ),*> ::der::ValueOrd for $newtype { + fn value_cmp(&self, other: &Self) -> ::der::Result<::core::cmp::Ordering> { + self.0.value_cmp(&other.0) + } + } + }; +} diff --git a/bt2x/bt2x-embedded/src/models/alpine.rs b/bt2x/bt2x-embedded/src/models/alpine.rs new file mode 100644 index 0000000..82d42bb --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/alpine.rs @@ -0,0 +1,39 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + +#[cfg(feature = "no_std")] +use alloc::collections::BTreeMap as Map; + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::log_entry_value::{Hash, PublicKey}; + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AlpineObj { + pub package: AlpinePackage, + pub public_key: PublicKey, + pub content: String, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AlpinePackage { + pub pkginfo: Map, + pub hash: Hash, + pub content: String, +} + + + + diff --git a/bt2x/bt2x-embedded/src/models/alpine_all_of.rs b/bt2x/bt2x-embedded/src/models/alpine_all_of.rs new file mode 100644 index 0000000..b2fecc7 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/alpine_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct AlpineAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Schema for Alpine package objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl AlpineAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> AlpineAllOf { + AlpineAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/consistency_proof.rs b/bt2x/bt2x-embedded/src/models/consistency_proof.rs new file mode 100644 index 0000000..04c2dfe --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/consistency_proof.rs @@ -0,0 +1,41 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[cfg(feature = "no_std")] +use alloc::vec::Vec; + +use serde_with::serde_as; +use serde_with::hex::Hex; + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsistencyProof { + /// The hash value stored at the root of the merkle tree at the time the proof was generated + #[serde_as(as = "Hex")] + pub root_hash: [u8; 32], + //String, + #[serde_as(as = "Vec")] + pub hashes: Vec<[u8; 32]>, +} + +impl ConsistencyProof { + pub fn new(root_hash: [u8; 32], hashes: Vec<[u8; 32]>) -> ConsistencyProof { + ConsistencyProof { + root_hash: root_hash.into(), + hashes, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/cose.rs b/bt2x/bt2x-embedded/src/models/cose.rs new file mode 100644 index 0000000..70ee4b3 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/cose.rs @@ -0,0 +1,36 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::log_entry_value::{Hash}; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CoseObj { + pub message: String, + pub public_key: String, + pub data: CoseData, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CoseData { + pub payload_hash: Hash, + pub envelope_hash: Hash, + pub aad: String, +} + + diff --git a/bt2x/bt2x-embedded/src/models/cose_all_of.rs b/bt2x/bt2x-embedded/src/models/cose_all_of.rs new file mode 100644 index 0000000..be50fcc --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/cose_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct CoseAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// COSE for Rekord objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl CoseAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> CoseAllOf { + CoseAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/error.rs b/bt2x/bt2x-embedded/src/models/error.rs new file mode 100644 index 0000000..bd1eae9 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/error.rs @@ -0,0 +1,31 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Error { + #[serde(rename = "code", skip_serializing_if = "Option::is_none")] + pub code: Option, + #[serde(rename = "message", skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +impl Error { + pub fn new() -> Error { + Error { + code: Option::<_>::None, + message: Option::<_>::None, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/hashedrekord.rs b/bt2x/bt2x-embedded/src/models/hashedrekord.rs new file mode 100644 index 0000000..7286bff --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/hashedrekord.rs @@ -0,0 +1,28 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + +use crate::models::log_entry_value::{Hash, Signature}; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HashedRekordObj { + pub data: HashRekordData, + pub signature: Signature, +} + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct HashRekordData { + pub hash: Hash, +} + + diff --git a/bt2x/bt2x-embedded/src/models/hashedrekord_all_of.rs b/bt2x/bt2x-embedded/src/models/hashedrekord_all_of.rs new file mode 100644 index 0000000..2e4356c --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/hashedrekord_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct HashedrekordAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Schema for Rekord objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl HashedrekordAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> HashedrekordAllOf { + HashedrekordAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/helm.rs b/bt2x/bt2x-embedded/src/models/helm.rs new file mode 100644 index 0000000..5fecded --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/helm.rs @@ -0,0 +1,48 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + + + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::log_entry_value::{Hash, PublicKey}; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HelmObj { + pub public_key: PublicKey, + pub chart: HelmChart, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HelmChart { + pub hash: Hash, + pub provenance: Provenance, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Provenance { + pub signature: ProvenanceSignature, + pub content: String, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProvenanceSignature { + pub content: String, +} + + diff --git a/bt2x/bt2x-embedded/src/models/helm_all_of.rs b/bt2x/bt2x-embedded/src/models/helm_all_of.rs new file mode 100644 index 0000000..e86c652 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/helm_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct HelmAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Schema for Helm objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl HelmAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> HelmAllOf { + HelmAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/inactive_shard_log_info.rs b/bt2x/bt2x-embedded/src/models/inactive_shard_log_info.rs new file mode 100644 index 0000000..3ff3e59 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/inactive_shard_log_info.rs @@ -0,0 +1,44 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + +#[cfg(feature = "no_std")] +use alloc::string::String; + +use serde_with::serde_as; +use serde_with::hex::Hex; + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all="camelCase")] +pub struct InactiveShardLogInfo { + /// The current hash value stored at the root of the merkle tree + #[serde_as(as = "Hex")] + pub root_hash: [u8; 32], /// The current number of nodes in the merkle tree + pub tree_size: u64, + /// The current signed tree head + pub signed_tree_head: String, + /// The current treeID + #[serde(rename = "treeID")] + pub tree_id: String, +} + +impl InactiveShardLogInfo { + pub fn new(root_hash: [u8; 32], tree_size: u64, signed_tree_head: String, tree_id: String) -> InactiveShardLogInfo { + InactiveShardLogInfo { + root_hash, + tree_size, + signed_tree_head, + tree_id, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/inclusion_proof.rs b/bt2x/bt2x-embedded/src/models/inclusion_proof.rs new file mode 100644 index 0000000..454067d --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/inclusion_proof.rs @@ -0,0 +1,54 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + +#[cfg(feature = "no_std")] +use alloc::vec::Vec; +#[cfg(feature = "no_std")] +use alloc::string::String; + + +use serde_with::serde_as; +use serde_with::hex::Hex; + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InclusionProof { + /// The index of the entry in the transparency log + #[serde(rename = "logIndex")] + pub log_index: i32, + /// The hash value stored at the root of the merkle tree at the time the proof was generated + #[serde_as(as = "Hex")] + pub root_hash: [u8; 32], + /// The size of the merkle tree at the time the inclusion proof was generated + #[serde(rename = "treeSize")] + pub tree_size: u64, + /// A list of hashes required to compute the inclusion proof, sorted in order from leaf to root + #[serde_as(as = "Vec")] + pub hashes: Vec<[u8; 32]>, + /// The checkpoint (signed tree head) that the inclusion proof is based on + #[serde(rename = "checkpoint")] + pub checkpoint: String, +} + +impl InclusionProof { + pub fn new(log_index: i32, root_hash: [u8; 32], tree_size: u64, hashes: Vec<[u8; 32]>, checkpoint: String) -> InclusionProof { + InclusionProof { + log_index, + root_hash, + tree_size, + hashes, + checkpoint, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/intoto.rs b/bt2x/bt2x-embedded/src/models/intoto.rs new file mode 100644 index 0000000..adad171 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/intoto.rs @@ -0,0 +1,36 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + + + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::log_entry_value::{Hash}; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InTotoObj { + pub content: InTotoContent, + pub public_key: String, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InTotoContent { + pub envelope: String, + pub hash: Hash, + pub payload_hash: Hash, +} + + diff --git a/bt2x/bt2x-embedded/src/models/intoto_all_of.rs b/bt2x/bt2x-embedded/src/models/intoto_all_of.rs new file mode 100644 index 0000000..6ebb850 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/intoto_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct IntotoAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Intoto for Rekord objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl IntotoAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> IntotoAllOf { + IntotoAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/jar.rs b/bt2x/bt2x-embedded/src/models/jar.rs new file mode 100644 index 0000000..a717781 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/jar.rs @@ -0,0 +1,34 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::log_entry_value::{Hash, Signature}; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JarObj { + pub signature: Signature, + pub archive: JarArchive, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JarArchive { + pub hash: Hash, + pub content: String, +} + + diff --git a/bt2x/bt2x-embedded/src/models/jar_all_of.rs b/bt2x/bt2x-embedded/src/models/jar_all_of.rs new file mode 100644 index 0000000..b0344fa --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/jar_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct JarAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Schema for JAR objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl JarAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> JarAllOf { + JarAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/log_entry_value.rs b/bt2x/bt2x-embedded/src/models/log_entry_value.rs new file mode 100644 index 0000000..8ab7a0c --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/log_entry_value.rs @@ -0,0 +1,176 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[cfg(feature = "no_std")] +use alloc::string::String; +#[cfg(feature = "no_std")] +use alloc::vec::Vec; +#[cfg(feature = "no_std")] +use alloc::collections::BTreeMap as Map; + +#[cfg(feature = "std")] +use HashMap as Map; + +#[cfg(feature = "no_std")] +use alloc::boxed::Box; + +use serde_with::serde_as; +use serde_with::hex::Hex; + +use serde_with::base64::{Base64, Standard}; +use serde_with::formats::{Padded}; + +use crate::models::alpine::AlpineObj; +use crate::models::cose::CoseObj; +use crate::models::hashedrekord::HashedRekordObj; +use crate::models::helm::HelmObj; +use crate::models::intoto::InTotoObj; +use crate::models::JarObj; +use crate::models::rekord::RekordObj; +use crate::models::rfc3161::Rfc3161Obj; +use crate::models::rpm::RpmObj; +use crate::models::tuf::TufObj; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct LogEntryValue { + /// This is the SHA256 hash of the DER-encoded public key for the log at the time the entry was included in the log + #[serde(rename = "LogID")] + pub log_id: String, + //String, + #[serde(rename = "LogIndex")] + pub log_index: i32, + #[serde(rename = "Body")] + pub body: LogEntryBody, + #[serde(rename = "IntegratedTime")] + pub integrated_time: i32, + #[serde(rename = "attestation", skip_serializing_if = "Option::is_none")] + pub attestation: Option>, + #[serde(rename = "verification", skip_serializing_if = "Option::is_none")] + pub verification: Option>, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum LogEntryBody { + #[serde(rename = "HashedRekordObj")] + HashedRekord(HashedRekordObj), + #[serde(rename = "Rfc3161Obj")] + Rfc3161(Rfc3161Obj), + #[serde(rename = "AlpineObj")] + Alpine(AlpineObj), + #[serde(rename = "CoseObj")] + Cose(CoseObj), + #[serde(rename = "HelmObj")] + Helm(HelmObj), + #[serde(rename = "InTotoObj")] + InToto(InTotoObj), + #[serde(rename = "JarObj")] + Jar(JarObj), + #[serde(rename = "RekordObj")] + Rekord(RekordObj), + #[serde(rename = "RpmObj")] + Rpm(RpmObj), + #[serde(rename = "TufObj")] + Tuf(TufObj), +} + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PgpKey { + pub content: String, +} + + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase", tag = "algorithm", content = "value")] +pub enum Hash { + Sha256( + #[serde_as(as = "Hex")] + [u8; 32] + ), +} + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Signature { + pub format: Option, + #[serde_as(as = "Base64")] + pub content: Vec, + pub public_key: PublicKey, +} + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKey { + #[serde_as(as = "Base64")] + pub content: Vec, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum SignatureFormat { + Pgp, + Minisign, + X509, + Ssh, +} + + +impl LogEntryValue { + pub fn new(log_id: &str, log_index: i32, body: LogEntryBody, integrated_time: i32) -> LogEntryValue { + LogEntryValue { + log_id: log_id.into(), + log_index, + body, + integrated_time, + attestation: Option::<_>::None, + verification: Option::<_>::None, + } + } +} + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct LogEntryValueAttestation { + #[serde(rename = "data", skip_serializing_if = "Option::is_none")] + pub data: Option>, +} + +impl LogEntryValueAttestation { + pub fn new() -> LogEntryValueAttestation { + LogEntryValueAttestation { + data: Option::<_>::None, + } + } +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct LogEntryValueVerification { + #[serde(rename = "inclusionProof", skip_serializing_if = "Option::is_none")] + pub inclusion_proof: Option>, + /// Signature over the logID, logIndex, body and integratedTime. + #[serde(rename = "signedEntryTimestamp", skip_serializing_if = "Option::is_none")] + pub signed_entry_timestamp: Option, +} + +impl LogEntryValueVerification { + pub fn new() -> LogEntryValueVerification { + LogEntryValueVerification { + inclusion_proof: Option::<_>::None, + signed_entry_timestamp: Option::<_>::None, + } + } +} \ No newline at end of file diff --git a/bt2x/bt2x-embedded/src/models/log_entry_value_attestation.rs b/bt2x/bt2x-embedded/src/models/log_entry_value_attestation.rs new file mode 100644 index 0000000..aef93fb --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/log_entry_value_attestation.rs @@ -0,0 +1,28 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct LogEntryValueAttestation { + #[serde(rename = "data", skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +impl LogEntryValueAttestation { + pub fn new() -> LogEntryValueAttestation { + LogEntryValueAttestation { + data: Option::<_>::None, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/log_entry_value_verification.rs b/bt2x/bt2x-embedded/src/models/log_entry_value_verification.rs new file mode 100644 index 0000000..683f1fe --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/log_entry_value_verification.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct LogEntryValueVerification { + #[serde(rename = "inclusionProof", skip_serializing_if = "Option::is_none")] + pub inclusion_proof: Option>, + /// Signature over the logID, logIndex, body and integratedTime. + #[serde(rename = "signedEntryTimestamp", skip_serializing_if = "Option::is_none")] + pub signed_entry_timestamp: Option, +} + +impl LogEntryValueVerification { + pub fn new() -> LogEntryValueVerification { + LogEntryValueVerification { + inclusion_proof: Option::<_>::None, + signed_entry_timestamp: Option::<_>::None, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/log_info.rs b/bt2x/bt2x-embedded/src/models/log_info.rs new file mode 100644 index 0000000..1c8d001 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/log_info.rs @@ -0,0 +1,51 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + +#[cfg(feature = "no_std")] +use alloc::string::String; +#[cfg(feature = "no_std")] +use alloc::vec::Vec; + +use serde_with::serde_as; +use serde_with::hex::Hex; + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LogInfo { + /// The current hash value stored at the root of the merkle tree + #[serde_as(as = "Hex")] + pub root_hash: [u8; 32], + /// The current number of nodes in the merkle tree + pub tree_size: u64, + /// The current signed tree head + pub signed_tree_head: String, + /// The current treeID + #[serde(rename = "treeID")] + pub tree_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub inactive_shards: Option>, +} + +impl LogInfo { + pub fn new(root_hash: [u8; 32], tree_size: u64, signed_tree_head: String, tree_id: String) -> LogInfo { + LogInfo { + root_hash, + tree_size, + signed_tree_head, + tree_id, + inactive_shards: Option::<_>::None, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/mod.rs b/bt2x/bt2x-embedded/src/models/mod.rs new file mode 100644 index 0000000..af3a6a0 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/mod.rs @@ -0,0 +1,45 @@ +//pub mod alpine; +//pub use self::alpine::AlpineObj; +// pub mod consistency_proof; +//pub use self::consistency_proof::ConsistencyProof; +//pub mod cose; +//pub use self::cose::CoseObj; +//pub mod error; +//pub use self::error::Error; +//pub mod hashedrekord; +//pub use self::hashedrekord::HashedRekordObj; +//pub mod helm; +//pub use self::helm::HelmObj; +//pub mod inactive_shard_log_info; +//pub use self::inactive_shard_log_info::InactiveShardLogInfo; +//pub mod inclusion_proof; +//pub use self::inclusion_proof::InclusionProof; +//pub mod intoto; +//pub use self::intoto::InTotoObj; +//pub mod jar; +//pub use self::jar::JarObj; +//pub mod log_entry_value; +//pub use self::log_entry_value::{ +// LogEntryValue, +// LogEntryValueAttestation, +// LogEntryValueVerification, +//}; +//pub mod log_info; +//pub use self::log_info::LogInfo; +//pub mod proposed_entry; +//pub use self::proposed_entry::ProposedEntry; +//pub mod rekord; +//pub use self::rekord::RekordObj; +//pub mod rfc3161; +//pub use self::rfc3161::Rfc3161Obj; +//pub mod rpm; +//pub use self::rpm::RpmObj; +//pub mod search_index; +//pub use self::search_index::SearchIndex; +//pub mod search_index_public_key; +//pub use self::search_index_public_key::SearchIndexPublicKey; +//pub mod search_log_query; +//pub use self::search_log_query::SearchLogQuery; +//pub mod tuf; +//pub use self::tuf::TufObj; +// \ No newline at end of file diff --git a/bt2x/bt2x-embedded/src/models/proposed_entry.rs b/bt2x/bt2x-embedded/src/models/proposed_entry.rs new file mode 100644 index 0000000..dc80ccd --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/proposed_entry.rs @@ -0,0 +1,109 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + +#[cfg(feature = "no_std")] +use alloc::string::String; + + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::alpine::AlpineObj; +use crate::models::cose::CoseObj; +use crate::models::{HashedRekordObj, HelmObj, InTotoObj, JarObj, RekordObj, Rfc3161Obj, RpmObj, TufObj}; + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "kind")] +pub enum ProposedEntry { + #[serde(rename = "alpine")] + Alpine { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for Alpine package objects + #[serde(rename = "spec")] + spec: AlpineObj, + }, + #[serde(rename = "cose")] + Cose { + #[serde(rename = "apiVersion")] + api_version: String, + /// COSE for Rekord objects + #[serde(rename = "spec")] + spec: CoseObj, + }, + #[serde(rename = "hashedrekord")] + HashedRekord { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for Rekord objects + #[serde(rename = "spec")] + spec: HashedRekordObj, + }, + #[serde(rename = "helm")] + Helm { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for Helm objects + #[serde(rename = "spec")] + spec: HelmObj, + }, + #[serde(rename = "intoto")] + InToto { + #[serde(rename = "apiVersion")] + api_version: String, + /// Intoto for Rekord objects + #[serde(rename = "spec")] + spec: InTotoObj, + }, + #[serde(rename = "jar")] + Jar { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for JAR objects + #[serde(rename = "spec")] + spec: JarObj, + }, + #[serde(rename = "rekord")] + Rekord { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for Rekord objects + #[serde(rename = "spec")] + spec: RekordObj, + }, + #[serde(rename = "rfc3161")] + Rfc3161 { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for RFC 3161 timestamp objects + #[serde(rename = "spec")] + spec: Rfc3161Obj, + }, + #[serde(rename = "rpm")] + Rpm { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for RPM objects + #[serde(rename = "spec")] + spec: RpmObj, + }, + #[serde(rename = "tuf")] + Tuf { + #[serde(rename = "apiVersion")] + api_version: String, + /// Schema for TUF metadata objects + #[serde(rename = "spec")] + spec: TufObj, + }, +} + + + + diff --git a/bt2x/bt2x-embedded/src/models/rekord.rs b/bt2x/bt2x-embedded/src/models/rekord.rs new file mode 100644 index 0000000..9e700d7 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/rekord.rs @@ -0,0 +1,34 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + + + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::log_entry_value::{Hash, Signature}; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RekordObj { + data: RekordData, + signature: Signature, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct RekordData { + pub content: String, + pub hash: Hash, +} + + diff --git a/bt2x/bt2x-embedded/src/models/rekord_all_of.rs b/bt2x/bt2x-embedded/src/models/rekord_all_of.rs new file mode 100644 index 0000000..47fd759 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/rekord_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct RekordAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Schema for Rekord objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl RekordAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> RekordAllOf { + RekordAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/rfc3161.rs b/bt2x/bt2x-embedded/src/models/rfc3161.rs new file mode 100644 index 0000000..8592690 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/rfc3161.rs @@ -0,0 +1,31 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + + +#[cfg(feature = "std")] +use HashMap as Map; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Rfc3161Obj { + pub tsr: Rfc3161Tsr, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Rfc3161Tsr { + pub content: String, +} + + diff --git a/bt2x/bt2x-embedded/src/models/rfc3161_all_of.rs b/bt2x/bt2x-embedded/src/models/rfc3161_all_of.rs new file mode 100644 index 0000000..b8a192f --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/rfc3161_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Rfc3161AllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Schema for RFC 3161 timestamp objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Rfc3161AllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> Rfc3161AllOf { + Rfc3161AllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/rpm.rs b/bt2x/bt2x-embedded/src/models/rpm.rs new file mode 100644 index 0000000..acecbe4 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/rpm.rs @@ -0,0 +1,37 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + +#[cfg(feature = "no_std")] +use alloc::collections::BTreeMap as Map; + +#[cfg(feature = "std")] +use HashMap as Map; +use crate::models::log_entry_value::{Hash, PgpKey}; + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpmObj { + pub public_key: PgpKey, + pub package: RpmPackage, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpmPackage { + pub headers: Map, + pub hash: Hash, + pub content: String, + +} + + diff --git a/bt2x/bt2x-embedded/src/models/rpm_all_of.rs b/bt2x/bt2x-embedded/src/models/rpm_all_of.rs new file mode 100644 index 0000000..4246704 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/rpm_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct RpmAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, + /// Schema for RPM objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl RpmAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> RpmAllOf { + RpmAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/schema/alpine_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/alpine_v0_0_1_schema.json new file mode 100644 index 0000000..2aa73a0 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/alpine_v0_0_1_schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/alpine/alpine_v0_0_1_schema.json", + "title": "Alpine v0.0.1 Schema", + "description": "Schema for Alpine Package entries", + "type": "object", + "properties": { + "publicKey" : { + "description": "The public key that can verify the package signature", + "type": "object", + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + } + }, + "required": [ "content" ] + }, + "package": { + "description": "Information about the package associated with the entry", + "type": "object", + "properties": { + "pkginfo": { + "description": "Values of the .PKGINFO key / value pairs", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "readOnly": true + }, + "hash": { + "description": "Specifies the hash algorithm and value for the package", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value for the package", + "type": "string" + } + }, + "readOnly": true, + "required": [ "algorithm", "value" ] + }, + "content": { + "description": "Specifies the package inline within the document", + "type": "string", + "format": "byte", + "writeOnly": true + } + }, + "oneOf": [ + { + "required": [ "hash" ] + }, + { + "required": [ "content" ] + } + ] + } + }, + "required": [ "publicKey", "package" ] +} diff --git a/bt2x/bt2x-embedded/src/models/schema/cose_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/cose_v0_0_1_schema.json new file mode 100644 index 0000000..cfc917d --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/cose_v0_0_1_schema.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/cose/cose_v0_0_1_schema.json", + "title": "cose v0.0.1 Schema", + "description": "Schema for cose object", + "type": "object", + "properties": { + "message": { + "description": "The COSE Sign1 Message", + "type": "string", + "format": "byte", + "writeOnly": true + }, + "publicKey": { + "description": "The public key that can verify the signature", + "type": "string", + "format": "byte" + }, + "data": { + "description": "Information about the content associated with the entry", + "type": "object", + "properties": { + "payloadHash": { + "description": "Specifies the hash algorithm and value for the content", + "type": "object", + "readOnly": true, + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the content", + "type": "string" + } + }, + "required": [ + "algorithm", + "value" + ] + }, + "envelopeHash": { + "description": "Specifies the hash algorithm and value for the COSE envelope", + "type": "object", + "readOnly": true, + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the envelope", + "type": "string" + } + }, + "required": [ + "algorithm", + "value" + ] + }, + "aad": { + "description": "Specifies the additional authenticated data required to verify the signature", + "type": "string", + "format": "byte", + "writeOnly": true + } + }, + "required": [] + } + }, + "required": [ + "publicKey", + "data" + ] +} diff --git a/bt2x/bt2x-embedded/src/models/schema/hashedrekord_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/hashedrekord_v0_0_1_schema.json new file mode 100644 index 0000000..8752ae6 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/hashedrekord_v0_0_1_schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/rekord/rekord_v0_0_1_schema.json", + "title": "Hashed Rekor v0.0.1 Schema", + "description": "Schema for Hashed Rekord object", + "type": "object", + "properties": { + "signature": { + "description": "Information about the detached signature associated with the entry", + "type": "object", + "properties": { + "content": { + "description": "Specifies the content of the signature inline within the document", + "type": "string", + "format": "byte" + }, + "publicKey" : { + "description": "The public key that can verify the signature; this can also be an X509 code signing certificate that contains the raw public key information", + "type": "object", + "properties": { + "content": { + "description": "Specifies the content of the public key or code signing certificate inline within the document", + "type": "string", + "format": "byte" + } + } + } + } + }, + "data": { + "description": "Information about the content associated with the entry", + "type": "object", + "properties": { + "hash": { + "description": "Specifies the hash algorithm and value for the content", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value for the content", + "type": "string" + } + }, + "required": [ "algorithm", "value" ] + } + } + } + }, + "required": [ "signature", "data" ] +} diff --git a/bt2x/bt2x-embedded/src/models/schema/helm_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/helm_v0_0_1_schema.json new file mode 100644 index 0000000..f48db2a --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/helm_v0_0_1_schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/helm/helm_v0_0_1_schema.json", + "title": "Helm v0.0.1 Schema", + "description": "Schema for Helm object", + "type": "object", + "properties": { + "publicKey": { + "description": "The public key that can verify the package signature", + "type": "object", + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + } + }, + "required": [ "content" ] + }, + "chart": { + "description": "Information about the Helm chart associated with the entry", + "type": "object", + "properties": { + "hash": { + "description": "Specifies the hash algorithm and value for the chart", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the chart", + "type": "string" + } + }, + "required": [ "algorithm", "value" ], + "readOnly": true + }, + "provenance": { + "description": "The provenance entry associated with the signed Helm Chart", + "type": "object", + "properties": { + "signature": { + "description": "Information about the included signature in the provenance file", + "type": "object", + "properties": { + "content": { + "description": "Specifies the signature embedded within the provenance file ", + "type": "string", + "format": "byte", + "readOnly": true + } + }, + "required": [ "content" ], + "readOnly": true + }, + "content": { + "description": "Specifies the content of the provenance file inline within the document", + "type": "string", + "format": "byte", + "writeOnly": true + } + }, + "oneOf": [ + { + "required": [ "signature" ] + }, + { + "required": [ "content" ] + } + ] + } + }, + "required": [ "provenance" ] + } + }, + "required": [ "publicKey", "chart" ] +} diff --git a/bt2x/bt2x-embedded/src/models/schema/intoto_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/intoto_v0_0_1_schema.json new file mode 100644 index 0000000..814f98e --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/intoto_v0_0_1_schema.json @@ -0,0 +1,70 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/intoto/intoto_v0_0_1_schema.json", + "title": "intoto v0.0.1 Schema", + "description": "Schema for intoto object", + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "envelope": { + "description": "envelope", + "type": "string", + "writeOnly": true + }, + "hash": { + "description": "Specifies the hash algorithm and value encompassing the entire signed envelope; this is computed by the rekor server, client-provided values are ignored", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the archive", + "type": "string" + } + }, + "required": [ + "algorithm", + "value" + ], + "readOnly": true + }, + "payloadHash": { + "description": "Specifies the hash algorithm and value covering the payload within the DSSE envelope; this is computed by the rekor server, client-provided values are ignored", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value for the envelope's payload", + "type": "string" + } + }, + "required": [ + "algorithm", + "value" + ], + "readOnly": true + } + } + }, + "publicKey": { + "description": "The public key that can verify the signature", + "type": "string", + "format": "byte" + } + }, + "required": [ + "publicKey", + "content" + ] +} \ No newline at end of file diff --git a/bt2x/bt2x-embedded/src/models/schema/jar_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/jar_v0_0_1_schema.json new file mode 100644 index 0000000..f5d06ed --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/jar_v0_0_1_schema.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/jar/jar_v0_0_1_schema.json", + "title": "JAR v0.0.1 Schema", + "description": "Schema for JAR entries", + "type": "object", + "properties": { + "signature": { + "description": "Information about the included signature in the JAR file", + "type": "object", + "properties": { + "content": { + "description": "Specifies the PKCS7 signature embedded within the JAR file ", + "type": "string", + "format": "byte", + "readOnly": true + }, + "publicKey" : { + "description": "The X509 certificate containing the public key JAR which verifies the signature of the JAR", + "type": "object", + "properties": { + "content": { + "description": "Specifies the content of the X509 certificate containing the public key used to verify the signature", + "type": "string", + "format": "byte" + } + }, + "required": [ "content" ], + "readOnly": true + } + }, + "required": [ "publicKey", "content" ] + }, + "archive": { + "description": "Information about the archive associated with the entry", + "type": "object", + "properties": { + "hash": { + "description": "Specifies the hash algorithm and value encompassing the entire signed archive", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value for the archive", + "type": "string" + } + }, + "required": [ "algorithm", "value" ] + }, + "content": { + "description": "Specifies the archive inline within the document", + "type": "string", + "format": "byte", + "writeOnly": true + } + }, + "oneOf": [ + { + "required": [ "hash" ] + }, + { + "required": [ "content" ] + } + ] + } + }, + "required": [ "archive" ] +} \ No newline at end of file diff --git a/bt2x/bt2x-embedded/src/models/schema/rekord_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/rekord_v0_0_1_schema.json new file mode 100644 index 0000000..aa809c6 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/rekord_v0_0_1_schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/rekord/rekord_v0_0_1_schema.json", + "title": "Rekor v0.0.1 Schema", + "description": "Schema for Rekord object", + "type": "object", + "properties": { + "signature": { + "description": "Information about the detached signature associated with the entry", + "type": "object", + "properties": { + "format": { + "description": "Specifies the format of the signature", + "type": "string", + "enum": [ "pgp", "minisign", "x509", "ssh" ] + }, + "content": { + "description": "Specifies the content of the signature inline within the document", + "type": "string", + "format": "byte" + }, + "publicKey" : { + "description": "The public key that can verify the signature", + "type": "object", + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + } + }, + "required": [ "content" ] + } + }, + "required": [ "format", "publicKey", "content" ] + }, + "data": { + "description": "Information about the content associated with the entry", + "type": "object", + "properties": { + "hash": { + "description": "Specifies the hash algorithm and value for the content", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value for the content", + "type": "string" + } + }, + "required": [ "algorithm", "value" ], + "readOnly": true + }, + "content": { + "description": "Specifies the content inline within the document", + "type": "string", + "format": "byte", + "writeOnly": true + } + }, + "oneOf": [ + { + "required": [ "hash" ] + }, + { + "required": [ "content" ] + } + ] + } + }, + "required": [ "signature", "data" ] +} diff --git a/bt2x/bt2x-embedded/src/models/schema/rfc3161_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/rfc3161_v0_0_1_schema.json new file mode 100644 index 0000000..de9e2f9 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/rfc3161_v0_0_1_schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/timestamp/timestamp_v0_0_1_schema.json", + "title": "Timestamp v0.0.1 Schema", + "description": "Schema for RFC3161 entries", + "type": "object", + "properties": { + "tsr": { + "description": "Information about the tsr file associated with the entry", + "type": "object", + "properties" : { + "content": { + "description": "Specifies the tsr file content inline within the document", + "type": "string", + "format": "byte" + } + }, + "required": [ "content" ] + } + }, + "required": [ "tsr" ] +} diff --git a/bt2x/bt2x-embedded/src/models/schema/rpm_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/rpm_v0_0_1_schema.json new file mode 100644 index 0000000..f6e2c45 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/rpm_v0_0_1_schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/rpm/rpm_v0_0_1_schema.json", + "title": "RPM v0.0.1 Schema", + "description": "Schema for RPM entries", + "type": "object", + "properties": { + "publicKey" : { + "description": "The PGP public key that can verify the RPM signature", + "type": "object", + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + } + }, + "required": [ "content" ] + }, + "package": { + "description": "Information about the package associated with the entry", + "type": "object", + "properties": { + "headers": { + "description": "Values of the RPM headers", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "readOnly": true + }, + "hash": { + "description": "Specifies the hash algorithm and value for the package", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ "sha256" ] + }, + "value": { + "description": "The hash value for the package", + "type": "string" + } + }, + "required": [ "algorithm", "value" ] + }, + "content": { + "description": "Specifies the package inline within the document", + "type": "string", + "format": "byte", + "writeOnly": true + } + }, + "oneOf": [ + { + "required": [ "hash" ] + }, + { + "required": [ "content" ] + } + ] + } + }, + "required": [ "publicKey", "package" ] +} diff --git a/bt2x/bt2x-embedded/src/models/schema/tuf_v0_0_1_schema.json b/bt2x/bt2x-embedded/src/models/schema/tuf_v0_0_1_schema.json new file mode 100644 index 0000000..f1a6cd3 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/schema/tuf_v0_0_1_schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/tuf/tuf_v0_0_1_schema.json", + "title": "TUF v0.0.1 Schema", + "description": "Schema for TUF metadata entries", + "type": "object", + "properties": { + "spec_version": { + "description": "TUF specification version", + "type": "string", + "readOnly": true + }, + "metadata": { + "description": "TUF metadata", + "type": "object", + "properties": { + "content": { + "description": "Specifies the metadata inline within the document", + "type": "object", + "additionalProperties": true + } + }, + "required": [ "metadata" ] + }, + "root" : { + "description": "root metadata containing about the public keys used to sign the manifest", + "type": "object", + "properties": { + "content": { + "description": "Specifies the metadata inline within the document", + "type": "object", + "additionalProperties": true + } + }, + "required": [ "content" ] + } + }, + "required": [ "metadata" , "root"] +} diff --git a/bt2x/bt2x-embedded/src/models/search_index.rs b/bt2x/bt2x-embedded/src/models/search_index.rs new file mode 100644 index 0000000..e012f43 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/search_index.rs @@ -0,0 +1,62 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + + +#[cfg(feature = "no_std")] +use alloc::string::String; +#[cfg(feature = "no_std")] +use alloc::boxed::Box; +use serde_with::serde_as; +use serde_with::hex::Hex; + +#[serde_as] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SearchIndex { + #[serde(skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub public_key: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(as = "Option>")] + pub hash: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub operator: Option, +} + +impl SearchIndex { + pub fn new() -> SearchIndex { + SearchIndex { + email: Option::<_>::None, + public_key: Option::<_>::None, + hash: Option::<_>::None, + operator: Option::<_>::None, + } + } +} + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize)] +pub enum Operator { + #[serde(rename = "and")] + And, + #[serde(rename = "or")] + Or, +} + +impl Default for Operator { + fn default() -> Operator { + Self::And + } +} + diff --git a/bt2x/bt2x-embedded/src/models/search_index_public_key.rs b/bt2x/bt2x-embedded/src/models/search_index_public_key.rs new file mode 100644 index 0000000..801f5bc --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/search_index_public_key.rs @@ -0,0 +1,55 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + +#[cfg(feature = "no_std")] +use alloc::string::String; + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all="camelCase")] +pub struct SearchIndexPublicKey { + #[serde(rename = "format")] + pub format: Format, + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +impl SearchIndexPublicKey { + pub fn new(format: Format) -> SearchIndexPublicKey { + SearchIndexPublicKey { + format, + content: Option::<_>::None, + url: Option::<_>::None, + } + } +} + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize)] +#[serde(rename_all="camelCase")] +pub enum Format { + Pgp, + X509, + Minisign, + Ssh, + Tuf, +} + +impl Default for Format { + fn default() -> Format { + Self::Pgp + } +} + diff --git a/bt2x/bt2x-embedded/src/models/search_log_query.rs b/bt2x/bt2x-embedded/src/models/search_log_query.rs new file mode 100644 index 0000000..0611bb7 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/search_log_query.rs @@ -0,0 +1,39 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + +#[cfg(feature = "no_std")] +use alloc::vec::Vec; +#[cfg(feature = "no_std")] +use alloc::string::String; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SearchLogQuery { + #[serde(rename = "entryUUIDs", skip_serializing_if = "Option::is_none")] + pub entry_uuids: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub log_indexes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub entries: Option>, +} + +impl SearchLogQuery { + pub fn new() -> SearchLogQuery { + SearchLogQuery { + entry_uuids: Option::>::None, + log_indexes: Option::<_>::None, + entries: Option::<_>::None, + } + } +} + + diff --git a/bt2x/bt2x-embedded/src/models/tuf.rs b/bt2x/bt2x-embedded/src/models/tuf.rs new file mode 100644 index 0000000..6c8c657 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/tuf.rs @@ -0,0 +1,34 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +#[cfg(feature = "no_std")] +use alloc::string::String; + +#[cfg(feature = "no_std")] +use alloc::collections::BTreeMap as Map; + +#[cfg(feature = "std")] +use HashMap as Map; + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TufObj { + #[serde(rename = "snake_case")] //https://github.com/sigstore/rekor/blob/ee83776f8b3b3c44829521d36472f4cac5c81f4d/pkg/types/tuf/v0.0.1/tuf_v0_0_1_schema.json#L8 + pub spec_version: String, + pub metadata: TufMetaData, + pub root: TufRoot, +} + +pub type TufRoot = Map; +pub type TufMetaData = Map; + + + diff --git a/bt2x/bt2x-embedded/src/models/tuf_all_of.rs b/bt2x/bt2x-embedded/src/models/tuf_all_of.rs new file mode 100644 index 0000000..76cd487 --- /dev/null +++ b/bt2x/bt2x-embedded/src/models/tuf_all_of.rs @@ -0,0 +1,32 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + + + + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct TufAllOf { + #[serde(rename = "apiVersion")] + pub api_version: alloc::string::String, //String, + /// Schema for TUF metadata objects + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl TufAllOf { + pub fn new(api_version: &str, spec: serde_json::Value) -> TufAllOf { + TufAllOf { + api_version: api_version.into(), + spec, + } + } +} + + diff --git a/bt2x/bt2x-flasher/Cargo.toml b/bt2x/bt2x-flasher/Cargo.toml new file mode 100644 index 0000000..db36230 --- /dev/null +++ b/bt2x/bt2x-flasher/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bt2x-flasher" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +probe-rs = { workspace = true } +bt2x-common = { path = "../bt2x-common" } +sigstore = { workspace = true } +tokio = { workspace = true, features = ["full"] } +futures = { workspace = true } +clap = { workspace = true, features = ["derive"] } +oci-distribution = { workspace = true, features = ["rustls-tls"] } +url = { workspace = true } +tracing-subscriber = { workspace = true, features = ["fmt", "std", "env-filter"] } +tracing = { workspace = true } +tracing-core = { workspace = true } +humantime = { workspace = true } +serde = { workspace = true } +anyhow = { workspace = true } +serde_yaml = { workspace = true } +serde_json = { workspace = true } +base64 = { workspace = true } +thiserror = { workspace = true } +rustls-pki-types = { workspace = true } +rustls-pemfile = { workspace = true } diff --git a/bt2x/bt2x-flasher/src/args.rs b/bt2x/bt2x-flasher/src/args.rs new file mode 100644 index 0000000..6c2144a --- /dev/null +++ b/bt2x/bt2x-flasher/src/args.rs @@ -0,0 +1,57 @@ +use clap::Parser; +use sigstore::registry::OciReference; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +#[clap(author, version /*, trailing_var_arg = true*/)] +pub struct Args { + #[arg(long)] + pub image: OciReference, + #[arg(long)] + pub target: String, + #[arg(long)] + pub subject: String, + #[arg(long)] + pub oidc_issuer: url::Url, + #[arg(long)] + pub format: probe_rs::flashing::Format, + #[arg(long)] + pub do_not_flash: bool, + #[arg(long)] + pub log_level: LogLevel, + #[arg(long)] + pub check_inclusion: bool, + #[arg(long)] + pub tuf_root: PathBuf, + #[arg(long)] + pub tuf_meta: PathBuf, + #[arg(long)] + pub(crate) fulcio_url: url::Url, + #[arg(long)] + pub(crate) rekor_url: url::Url, + #[arg(long)] + pub(crate) http: bool, + #[arg(long)] + pub(crate) base_address: Option, +} + +#[derive(clap::ValueEnum, Clone, Debug)] +pub enum LogLevel { + Debug, + Info, + Warn, + Error, + Trace, +} + +impl From<&LogLevel> for tracing_core::LevelFilter { + fn from(value: &LogLevel) -> Self { + match value { + LogLevel::Debug => tracing_core::Level::DEBUG.into(), + LogLevel::Info => tracing_core::Level::INFO.into(), + LogLevel::Warn => tracing_core::Level::WARN.into(), + LogLevel::Error => tracing_core::Level::ERROR.into(), + LogLevel::Trace => tracing_core::Level::TRACE.into(), + } + } +} diff --git a/bt2x/bt2x-flasher/src/main.rs b/bt2x/bt2x-flasher/src/main.rs new file mode 100644 index 0000000..8d9c197 --- /dev/null +++ b/bt2x/bt2x-flasher/src/main.rs @@ -0,0 +1,244 @@ +mod args; + +use anyhow::anyhow; +use bt2x_common::artifact::source::oci::OciSource; +use bt2x_common::error::Bt2XError; +use bt2x_common::rekor::Configuration as RekorConfig; +use bt2x_common::rekor::RekorClient; +use bt2x_common::sigstore_config::{KeyConfig, SigstoreConfig, TufTargetNames}; +use bt2x_common::tuf::load_tuf_filesystem; +use bt2x_common::verifier::bt::BinaryTransparencyVerifier; +use bt2x_common::verifier::bt::SigstoreKeys; +use bt2x_common::verifier::Artifact; +use bt2x_common::verifier::Verifier; +use clap::Parser; +use oci_distribution::client as oci_client; +use oci_distribution::secrets::RegistryAuth; +use probe_rs::Permissions; +use probe_rs::Session; +use rustls_pemfile::{read_one, Item}; +use sigstore::cosign::bundle::SignedArtifactBundle; +use sigstore::cosign::verification_constraint::CertSubjectEmailVerifier; +use sigstore::cosign::verification_constraint::VerificationConstraintVec; +use sigstore::cosign::CosignCapabilities; +use sigstore::crypto::{CosignVerificationKey, SigningScheme}; +use sigstore::registry::{ClientConfig, ClientProtocol, OciReference}; +use sigstore::rekor::apis::configuration::Configuration; +use sigstore::trust::ManualTrustRoot; +use std::fs; +use std::fs::OpenOptions; +use std::io::Write; +use std::io::{BufReader, Cursor}; +use std::panic; +use std::path::Path; +use std::path::PathBuf; +use thiserror::Error; +use tracing::error; +use tracing::{debug, info, warn}; +use tracing_subscriber::filter::Targets; +use tracing_subscriber::prelude::*; +use url::Url; + +#[derive(Debug, Error)] +pub enum FlasherError { + #[error("could not attach to probe {0}")] + AttachToProbeFailed(#[from] probe_rs::Error), + #[error("failed to flash {0}")] + FlashFailed(#[from] probe_rs::flashing::FileDownloadError), + #[error("failed to open sigstore config file {0}")] + FailedToOpenSigstoreConfig(std::io::Error), + #[error("failed to open bundle {0}")] + FailedToOpenBundle(std::io::Error), + #[error("failed to open binary {0}")] + FailedToOpenBinary(std::io::Error), + #[error("failed to open rekor public key {0}")] + FailedToOpenRekorPublicKey(std::io::Error), + #[error("failed to open CT log public key {0}")] + FailedToOpenCtLogPublicKey(std::io::Error), + #[error("failed to open fulcio root cert {0}")] + FailedToOpenFulcioRootCert(std::io::Error), + #[error("sigstore config file is invalid{0}")] + InvalidSigstoreConfig(#[from] serde_yaml::Error), + #[error("sigstore internal error {0}")] + InternalSigstoreError(#[from] sigstore::errors::SigstoreError), + #[error("bundle verification failed {0}")] + VerificationFailed(sigstore::errors::SigstoreError), + #[error("failed to fetch log entry")] + FetchLogEntryError, + #[error("inclusion proof failed")] + InclusionProofFailed, + #[error("{0:?}")] + TufError(Bt2XError), +} + +#[tokio::main] +async fn main() -> Result<(), FlasherError> { + let args = args::Args::parse(); + configure_logging(&args); + let sigstore_keys = load_tuf_filesystem( + &args.tuf_root, + &url::Url::from_file_path(&args.tuf_meta.canonicalize().unwrap()).unwrap(), + &url::Url::from_file_path(args.tuf_meta.join("/tmp")).unwrap(), + &TufTargetNames { + ctlog: String::from("ctlog.pub"), + rekor: String::from("rekor.pub"), + fulcio: String::from("fulcio.crt.pem"), + }, + ) + .await + .map_err(FlasherError::TufError)?; + + // setup clients and configs for clients + let rekor_client = RekorClient::new(RekorConfig { + base_path: args.rekor_url.to_string(), + ..Default::default() + }); + + let registry_config = ClientConfig { + protocol: if args.http { + ClientProtocol::Http + } else { + ClientProtocol::Https + }, + accept_invalid_certificates: false, + extra_root_certificates: vec![], + accept_invalid_hostnames: true, + }; + let mut reader = BufReader::new(Cursor::new(&sigstore_keys.fulcio_cert)); + let Some(Item::X509Certificate(fulcio_cert_der)) = + read_one(&mut reader).expect("failed to decode PEM") + else { + panic!("error converting parsing fulcio cert") + }; + let mut reader = BufReader::new(Cursor::new(&sigstore_keys.rekor_key)); + let Some(Item::SubjectPublicKeyInfo(rekor_key_der)) = + read_one(&mut reader).expect("failed to decode PEM") + else { + panic!("error converting parsing rekor key") + }; + let mut reader = BufReader::new(Cursor::new(&sigstore_keys.ctlog_key)); + let Some(Item::SubjectPublicKeyInfo(ctlog_key_der)) = + read_one(&mut reader).expect("failed to decode PEM") + else { + panic!("error converting parsing the CT log key") + }; + + let cosign_client = sigstore::cosign::ClientBuilder::default() + .with_trust_repository(&ManualTrustRoot { + ctfe_keys: vec![ctlog_key_der.to_vec()], + rekor_keys: vec![rekor_key_der.to_vec()], + fulcio_certs: vec![fulcio_cert_der], + }) + .expect("failed to add trust repo") + .with_oci_client_config(registry_config) + .build() + .expect("Unexpected failure while building Client"); + + let oci_client_config = oci_client::ClientConfig { + protocol: if args.http { + oci_client::ClientProtocol::Http + } else { + oci_client::ClientProtocol::Https + }, + ..Default::default() + }; + + // configure verifier + let mut verifier = BinaryTransparencyVerifier::builder() + .monitors(vec![Url::parse("http://localhost:3132").unwrap()]) + .rekor_client(rekor_client) + .cosign_client(cosign_client) + .keys( + SigstoreKeys::builder() + .rekor_pub_key(String::from_utf8(sigstore_keys.rekor_key).unwrap()) + .fulcio_cert(String::from_utf8(sigstore_keys.fulcio_cert).unwrap()) + .ct_log_key(String::from_utf8(sigstore_keys.ctlog_key).unwrap()) + .build(), + ) + .build(); + + // configure source from which images are downloaded + let mut oci_source = OciSource::new(oci_client_config, RegistryAuth::Anonymous); + let mut constraints: VerificationConstraintVec = vec![Box::new(CertSubjectEmailVerifier { + email: args.subject, + issuer: Some(args.oidc_issuer.to_string()), + })]; + + let binary = match verifier + .verify(&mut oci_source, &args.image, &constraints) + .await + { + Ok(Artifact::Binary(binary)) => binary, + Ok(Artifact::BundledBinary { binary, bundle: _ }) => binary, + Err(err) => panic!("{err:?}"), + }; + let flasher_directory = PathBuf::from(".flasher"); + let file_path = + build_path(&flasher_directory, &args.image).expect("failed to build output path"); + std::fs::create_dir_all(flasher_directory).expect("failed to create flasher directory"); + let mut f = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&file_path) + .expect("failed to open output file"); + f.write_all(&binary) + .expect("failed to write binary to disk"); + + if args.do_not_flash { + warn!("skipping flashing due to `--do-not-flash` flag"); + return Ok(()); + } + let mut session = Session::auto_attach(args.target, Permissions::default())?; + debug!("{session:?}"); + info!("flashing binary"); + let probe_rs::flashing::Format::Bin(mut options) = args.format else { + panic!("only supports bin formats"); + }; + options.base_address = args.base_address.or(Some(0x10000000)); + probe_rs::flashing::download_file( + &mut session, + &file_path, + probe_rs::flashing::Format::Bin(options), + )?; + info!("flashing complete"); + Ok(()) +} + +fn configure_logging(cli: &args::Args) { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with( + Targets::new() + .with_target(env!("CARGO_PKG_NAME").replace('-', "_"), &cli.log_level) + .with_target("bt2x_common", &cli.log_level) + .with_target("sigstore", tracing_core::Level::INFO), + ) + .init(); +} + +/// Builds a file path from the OCI reference at which the output file is stored. +fn build_path(base_path: &Path, reference: &OciReference) -> anyhow::Result { + let out_path = PathBuf::from(reference.repository()); + let out_path = format!( + "{}-{}.{}", + out_path + .file_stem() + .ok_or(anyhow!("could not create file name from reference")) + .and_then(|s| s + .to_str() + .ok_or(anyhow!("could not get unicode string from path")))?, + reference + .digest() + .or(reference.tag()) + .ok_or(anyhow!("OCI reference requires tag or digest")) + .map(|s| s.trim_start_matches("sha256:"))?, + out_path + .extension() + .unwrap_or("".as_ref()) + .to_str() + .unwrap() + ); + let out_path = out_path.trim_start_matches('/').trim_end_matches('.'); + Ok(base_path.join(out_path)) +} diff --git a/bt2x/bt2x-monitor/Cargo.toml b/bt2x/bt2x-monitor/Cargo.toml new file mode 100644 index 0000000..169ec39 --- /dev/null +++ b/bt2x/bt2x-monitor/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bt2x-monitor" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { workspace = true, features = ["full"] } +axum = { workspace = true } +serde = { workspace = true, features = ["derive"] } +crossbeam = { workspace = true } +tracing = { workspace = true } +tracing-core = { workspace = true } +tracing-subscriber = { workspace = true, features = ["fmt", "std", "env-filter"] } +url = { workspace = true, features = ["serde"] } +uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] } +anyhow = { workspace = true } +thiserror = { workspace = true } +reqwest = { workspace = true } +sigstore = { workspace = true } +bt2x-common = { path = "../bt2x-common" } +base64 = { workspace = true } +futures = { workspace = true } +clap = { workspace = true, features = ["derive"] } +humantime = { workspace = true } +serde_yaml = { workspace = true } + +[dev-dependencies] +hex-literal = { workspace = true } diff --git a/bt2x/bt2x-monitor/README.md b/bt2x/bt2x-monitor/README.md new file mode 100644 index 0000000..922591b --- /dev/null +++ b/bt2x/bt2x-monitor/README.md @@ -0,0 +1,26 @@ +# BT²X Monitor + +This directory consists of a monitor application as well as a monitor library, which can be used to include a monitor in other applications. +The gossiping "primitives" are implemented in the `bt2x-common` library in the the `bt2x-common::gossip` module. + +## HTTP Interface + +The gossiping protocol uses a HTTP interface. There are two kinds of actions: **listen** (server) and **speak** (client). +Listening is the act of providing an endpoint (at `/listen`) where checkpoints can be sent to as part of the speak act. +Incoming gossip is verified against the most recent known checkpoint and verified using an inclusion proof provided by the log. +When successful, the request is then replied with the most freshest checkpoint. +In case of failure an error is returned. + +## Verification + +Two checkpoints are verified for consistency in the following way: + +1. Verify that the incoming checkpoint was signed by the BT log Rekor. +2. Determine which checkpoint is the newest one +3. Request a consistency proof from the older tree size to the newer tree size. +4. Run a consistency proof with the proof hashes provided by the log. +5. Reply with a response indicating the result. + +## Federation + +The monitor can be federated with other instances of itself by adding the URL of the other monitor to the command. \ No newline at end of file diff --git a/bt2x/bt2x-monitor/src/cli.rs b/bt2x/bt2x-monitor/src/cli.rs new file mode 100644 index 0000000..9a5ade6 --- /dev/null +++ b/bt2x/bt2x-monitor/src/cli.rs @@ -0,0 +1,27 @@ +use clap::Parser; +use std::path::PathBuf; +use url::Url; + +/// Simple program to greet a person +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// path to a Sigstore configuration file, consult [bt2x_common::sigstore_config::SigstoreConfig] for more information. + #[arg(long)] + pub sigstore_config: PathBuf, + #[arg(long)] + /// actively gossip to the monitor at the specified URL + pub monitor: Vec, + #[arg(long)] + /// use the checkpoint at this path as the initial checkpoint + pub current_checkpoint: Option, + /// port for the gossiping HTTP server of this monitor + #[arg(long, default_value_t = 3131)] + pub port: u16, + /// interval at which the Rekor log is queried for new checkpoints + #[arg(long)] + pub log_interval: humantime::Duration, + /// interval at which checkpoints are sent to other monitors + #[arg(long)] + pub gossip_interval: humantime::Duration, +} diff --git a/bt2x/bt2x-monitor/src/lib.rs b/bt2x/bt2x-monitor/src/lib.rs new file mode 100644 index 0000000..8a00337 --- /dev/null +++ b/bt2x/bt2x-monitor/src/lib.rs @@ -0,0 +1,286 @@ +use std::fs; +use std::fs::read_to_string; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::{anyhow, Context, Result}; +use axum::extract::Json; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::{extract::State, routing::get, Router}; +use futures::{stream, StreamExt}; +use sigstore::crypto::CosignVerificationKey; +use sigstore::rekor::apis::configuration::Configuration; +use tokio::sync::RwLock; +use tokio::time::sleep; +use tracing::{debug, info, warn}; + +use bt2x_common::gossip::*; +use bt2x_common::rekor::RekorClient; +use bt2x_common::sigstore_config::{KeyConfig, SigstoreConfig}; +use bt2x_common::tuf::load_tuf_filesystem; + +use crate::cli::Args; + +pub mod cli; + +#[derive(Debug)] +struct AppState { + pub current_checkpoint: Arc>, + pub rekor_key: Arc, + pub rekor_config: Arc, +} + +async fn get_err() -> impl IntoResponse { + (StatusCode::BAD_REQUEST, "Use POST.") +} + +async fn listen( + State(state): State>, + Json(checkpoint): Json, +) -> impl IntoResponse { + debug!("got request {checkpoint:?}"); + let mut current_checkpoint = state.current_checkpoint.write().await; + let rekor_client = RekorClient::new((*state.rekor_config).clone()); + + if let Err(err) = checkpoint.verify_signature(&state.rekor_key) { + debug!("{err:?}"); + return ( + StatusCode::OK, + Json(GossipResponse::Failure( + MonitorError::FailedSignatureVerification { + request_data: checkpoint, + pubkey: (), + }, + )), + ); + } + debug!("signature verified successfully"); + match verify_checkpoints( + (*current_checkpoint).clone(), + checkpoint.clone(), + &rekor_client, + ) + .await + { + Ok(new_checkpoint) => { + debug!("consistency verified successfully"); + *current_checkpoint = new_checkpoint.clone(); + ( + StatusCode::OK, + Json(GossipResponse::Success(new_checkpoint.clone())), + ) + } + Err(err) => { + warn!("failed to verify consistency: {err:?}"); + ( + StatusCode::OK, + Json(GossipResponse::Failure(MonitorError::Inconsistent { + request_data: checkpoint, + other: current_checkpoint.clone(), + })), + ) + } + } +} + +pub async fn main_impl(args: Args) -> Result<()> { + info!("starting monitor ..."); + let sigstore_config: SigstoreConfig = fs::read(&args.sigstore_config) + .context("failed to open sigstore config file") + .and_then(|config| { + serde_yaml::from_slice(&config).context("failed to parse sigstore config") + })?; + debug!("using config: {sigstore_config:?}"); + let rekor_key = match sigstore_config.key_config { + KeyConfig::Tuf { + metadata_base, + targets_base, + root_path, + target_names, + } => { + debug!("loading Sigstore keys from TUF repo via the file system ..."); + load_tuf_filesystem(&root_path, &metadata_base, &targets_base, &target_names) + .await + .map_err(|_| anyhow!("failed to load TUF from file system"))? + .rekor_key + } + KeyConfig::Keys { + rekor_key: Some(rekor_key), + .. + } => { + debug!("loading rekor key at {rekor_key:?}"); + fs::read(rekor_key).context("failed to read rekor key")? + } + _ => panic!("rekor key required, update config"), + }; + let rekor_key = CosignVerificationKey::from_pem(&rekor_key, &Default::default()) + .context("failed to parse rekor key")?; + let rekor_config = Arc::new(Configuration { + base_path: sigstore_config.urls.rekor.to_string(), + ..Default::default() + }); + + let current_checkpoint: Checkpoint = match args.current_checkpoint { + Some(current_checkpoint) => { + debug!("loading checkpoint from {current_checkpoint:?} ..."); + read_to_string(current_checkpoint) + .context("failed to read checkpoint at path: {current_checkpoint:?}") + .and_then(|data| data.parse().context("failed to parse checkpoint")) + .context("failed to load current checkpoint")? + } + None => { + debug!("no checkpoint provided, fetching log info using {rekor_config:?} ..."); + loop { + let res = sigstore::rekor::apis::tlog_api::get_log_info(&rekor_config) + .await + .context("failed to get log info") + .and_then(|log_info| { + Checkpoint::from_str(&log_info.signed_tree_head) + .context("failed to parse checkpoint from log info") + })?; + if res.tree_size > 0 { + break res; + } + info!("log provided checkpoint with tree_size == 0, fetching again later"); + sleep(Duration::from_secs(5)).await + } + } + }; + debug!("verifying the initial checkpoint ..."); + current_checkpoint + .verify_signature(&rekor_key) + .context("failed to verify signature")?; + debug!("successfully verified {current_checkpoint:?}"); + let rekor_key = Arc::new(rekor_key); + let current_checkpoint = Arc::new(RwLock::new(current_checkpoint)); + + let listeners = args.monitor.clone(); + + let shared_state = Arc::new(AppState { + rekor_config: rekor_config.clone(), + rekor_key: rekor_key.clone(), + current_checkpoint: current_checkpoint.clone(), + }); + + info!("starting speaker"); + let rekor_config_arc = rekor_config.clone(); + let rekor_key_arc = rekor_key.clone(); + let current_checkpoint_arc = current_checkpoint.clone(); + + let speaker = tokio::spawn(async move { + info!("speaker task spawned"); + let interval = tokio::time::interval(args.gossip_interval.into()); + let rekor_client = RekorClient::new((*rekor_config_arc).clone()); + let forever = stream::unfold(interval, |mut interval| async { + interval.tick().await; + debug!("speaker is active ..."); + for url in listeners.iter() { + let mut current_checkpoint = current_checkpoint_arc.write().await; + let Ok(response_data) = send_checkpoint(url, ¤t_checkpoint).await else { + warn!("sending checkpoint failed"); + continue; + }; + + let new_checkpoint = match response_data { + GossipResponse::Success(new) => new, + GossipResponse::Failure(err) => { + warn!("got failure response {err:#?}"); + continue; + } + }; + if let Err(err) = new_checkpoint.verify_signature(&rekor_key_arc) { + warn!("could not verify checkpoint {new_checkpoint:?} because of {err:?}"); + continue; + } + match verify_checkpoints( + (*current_checkpoint).clone(), + new_checkpoint, + &rekor_client, + ) + .await + { + Ok(new) => *current_checkpoint = new, + Err(err) => { + warn!("could not verify consistency {err:?}"); + continue; + } + } + } + debug!("speaker finished gossiping"); + Some(((), interval)) + }); + forever.for_each(|_| async {}).await; + }); + info!("starting poller task"); + let poller = tokio::spawn(async move { + info!("poller task has started"); + let interval = tokio::time::interval(args.log_interval.into()); + let rekor_client = RekorClient::new((*rekor_config).clone()); + let forever = stream::unfold(interval, |mut interval| async { + interval.tick().await; + debug!("poller is active ..."); + match fetch_and_verify_signature(&rekor_client, &rekor_key).await { + Ok(checkpoint) => { + let mut current_checkpoint = current_checkpoint.write().await; + let new = verify_checkpoints( + (*current_checkpoint).clone(), + checkpoint, + &rekor_client, + ) + .await + .expect("poller failed to verify checkpoint, received from the log"); + info!("using new checkpoint {new:?}"); + *current_checkpoint = new; + } + Err(err) => { + warn!("{err:?}") + } + } + Some(((), interval)) + }); + forever.for_each(|_| async {}).await; + }); + + info!("starting listener"); + let listener = tokio::spawn(async move { + info!("listener task spawned"); + let app = Router::new() + .route("/listen", get(get_err).post(listen)) + .with_state(shared_state); + + let listener = tokio::net::TcpListener::bind(&format!("0.0.0.0:{}", args.port)) + .await + .expect("failed to bind to socket"); + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); + }); + info!("main thread waiting"); + listener.await.context("failed to join listening server")?; + speaker.await.context("failed to join speaker loop")?; + poller.await.context("failed to join polling loop")?; + + info!("exiting ..."); + Ok(()) +} + +pub fn configure_logging(_args: &Args) { + use tracing_subscriber::filter::Targets; + use tracing_subscriber::prelude::*; + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with( + Targets::new() + .with_target( + env!("CARGO_PKG_NAME").replace('-', "_"), + tracing_core::Level::DEBUG, + ) + .with_target("sigstore", tracing_core::Level::INFO) + .with_target("oci_distribution", tracing_core::Level::DEBUG) + .with_target("hyper", tracing_core::Level::INFO) + .with_target("reqwest", tracing_core::Level::INFO), + ) + .init(); +} diff --git a/bt2x/bt2x-monitor/src/main.rs b/bt2x/bt2x-monitor/src/main.rs new file mode 100644 index 0000000..5faf6bd --- /dev/null +++ b/bt2x/bt2x-monitor/src/main.rs @@ -0,0 +1,9 @@ +use anyhow::Result; +use clap::Parser; + +#[tokio::main] +async fn main() -> Result<()> { + let args = bt2x_monitor::cli::Args::parse(); + bt2x_monitor::configure_logging(&args); + bt2x_monitor::main_impl(args).await +} diff --git a/bt2x/bt2x-ota-common/Cargo.toml b/bt2x/bt2x-ota-common/Cargo.toml new file mode 100644 index 0000000..c4212a0 --- /dev/null +++ b/bt2x/bt2x-ota-common/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "bt2x-ota-common" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[features] +default = ["tracing"] +defmt = ["dep:defmt", "embedded-io-async/defmt-03"] +tracing = ["dep:tracing"] +log = ["dep:log"] +mock_flash = [] + +[dependencies] +embedded-io-async = { workspace = true, features = [] } +embassy-net = { workspace = true, features = ["tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-time = { workspace = true, features = [] } +tuf-no-std = { path = "../../tuf-no-std/tuf-no-std", default-features = false, features = [ + "der", + "verify", + "ecdsa", + "async", +] } +defmt = { workspace = true, features = [], optional = true } +bitfield = { workspace = true, features = [] } +tracing = { workspace = true, default-features = false, optional = true, features = [] } +log = { workspace = true, default-features = false, optional = true, features = ["serde"] } +embedded-storage = { workspace = true } +sha2 = { workspace = true, default-features = false, features = [] } + +[dev-dependencies] +pipe = { workspace = true } +embedded-io-async = { workspace = true, features = ["std"] } +tokio = { workspace = true, features = ["full"] } +tempfile = { workspace = true } + diff --git a/bt2x/bt2x-ota-common/src/flash.rs b/bt2x/bt2x-ota-common/src/flash.rs new file mode 100644 index 0000000..c75e826 --- /dev/null +++ b/bt2x/bt2x-ota-common/src/flash.rs @@ -0,0 +1,75 @@ +pub use embedded_storage::nor_flash::NorFlash; +/// File-system-backed "flash" implementation for testing purposes. +#[cfg(any(feature = "mock_flash", test))] +pub mod mock_flash { + use alloc::vec; + pub use embedded_storage::nor_flash::{ + ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, + }; + use std::os::unix::fs::FileExt; + + #[derive(Debug)] + pub struct FsWriter(pub std::path::PathBuf); + + impl ReadNorFlash for FsWriter { + const READ_SIZE: usize = 0; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let f = std::fs::OpenOptions::new() + .read(true) + .open(&self.0) + .map_err(|_| FsWriterError(NorFlashErrorKind::Other))?; + f.read_exact_at(bytes, offset as u64) + .map_err(|_| FsWriterError(NorFlashErrorKind::Other))?; + Ok(()) + } + + fn capacity(&self) -> usize { + 0x4096 * 1024 * 1024 + } + } + + #[derive(Debug)] + pub struct FsWriterError(NorFlashErrorKind); + + impl NorFlashError for FsWriterError { + fn kind(&self) -> NorFlashErrorKind { + self.0 + } + } + + impl ErrorType for FsWriter { + type Error = FsWriterError; + } + + impl NorFlash for FsWriter { + const ERASE_SIZE: usize = 0; + const WRITE_SIZE: usize = 0; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + let data = vec![1; (to - from) as usize]; + let f = std::fs::OpenOptions::new() + .read(true) + .open(&self.0) + .map_err(|_| FsWriterError(NorFlashErrorKind::Other))?; + f.write_at(&data, from as u64) + .map_err(|_| FsWriterError(NorFlashErrorKind::Other))?; + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + let f = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + //.truncate(true) + .open(&self.0) + .expect("failed to open file"); + f.write_at(bytes, offset as u64).expect("failed to write"); + Ok(()) + } + } +} + +#[cfg(test)] +pub(crate) mod test {} diff --git a/bt2x/bt2x-ota-common/src/lib.rs b/bt2x/bt2x-ota-common/src/lib.rs new file mode 100644 index 0000000..3cc67ed --- /dev/null +++ b/bt2x/bt2x-ota-common/src/lib.rs @@ -0,0 +1,683 @@ +#![cfg_attr(all(not(test), not(feature = "mock_flash")), no_std)] +//! This crate contains the client side implementation for the OTA updates. +//! It is implemented using a custom TCP-based request-response protocol. +//! It works roughly in the following way: +//! - general structure of a message is `
[]`, +//! - handling the segmentation is left to TCP, +//! - the header specifies what type of message it is and what the rest of the header and message contains. +//! +//! Refer to the [Header] and the code for more information. +//! +//! ## Example +//! +//! ```ignore +//! use tuf_no_std::TufTransportAsync; +//! +//! // initialize_stack is not a real function, set up your network stack accordingly. +//! let stack = initialize_stack(); +//! let mut transport = Transport { +//! network_stack: &stack, +//! server: IpEndpoint::new( +//! "192.168.1.2", +//! 50000, +//! ), +//! }; +//! +//! let mut buf = [0u8; 4096]; +//! let binary = transport.fetch_binary( +//! b"binary-identifer", +//! &mut buf, +//! ).expect("failed to fetch binary"); +//! +//! ``` + +extern crate alloc; + +pub mod flash; +pub mod net; +/// TufTransportAsync implementation for `embassy_net` network stacks. +pub use net::Transport; + +use bitfield::bitfield; +use core::num::NonZeroU64; +#[cfg(feature = "defmt")] +use defmt::{debug, warn}; +#[cfg(feature = "log")] +use log::{debug, error, info, warn}; +#[cfg(feature = "tracing")] +use tracing::{debug, warn}; + +use embedded_io_async::{Read, Write}; +use embedded_storage::nor_flash::NorFlash; +use sha2::Digest; + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TufMessage { + Root = 0, + Timestamp = 1, + Snapshot = 2, + Targets = 3, + TargetFile = 4, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum MessageClass { + Request = 0, + Response = 1, + Error = 2, + Ok = 3, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum MessageType { + Binary = 0, + Signature = 1, + Tuf = 2, + Rfc3161 = 3, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum ErrorType { + NotEnoughSpace = 1, +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Write, + Read, + Flush, + WrongMsgType { + expected: TufMessage, + got: TufMessage, + }, + InvalidVarIntLen(u8), + FailedToDecodeVarInt, + FailedToEncodeVarInt, + OutBufTooSmall { + supplied: usize, + required: usize, + }, + RemoteClosed, + ReceivedInvalidMessage, + InvalidMessageClass(u128), + InvalidMessageType(u128), + InvalidContentType(u128), +} + +pub async fn fetch_tuf_role( + reader: impl Read, + writer: impl Write, + out: &mut [u8], + role: TufMessage, + version: Option, +) -> Result { + let version = version.unwrap_or(NonZeroU64::new(1).unwrap()); + //debug!("fetching {role:?}"); + + fetch_file( + reader, + writer, + out, + MessageClass::Request, + MessageType::Tuf, + role as u128, + Some(version.get() as u128), + None, + ) + .await +} + +pub async fn fetch_file( + mut reader: impl Read, + mut writer: impl Write, + out: &mut [u8], + message_class: MessageClass, + message_type: MessageType, + content_type: u128, + header_data: Option, + body: Option<&[u8]>, +) -> Result { + let mut header = Header(0); + header.set_message_class(message_class as u128); + header.set_message_type(message_type as u128); + header.set_content_type(content_type); + if let Some(header_data) = header_data { + header.set_header_data(header_data); + } + header.set_content_len(body.map(|b| b.len()).unwrap_or_default() as u128); + debug!("content len: {}", header.content_len()); + + debug!("writing request header to socket"); + writer.write(&header.0.to_be_bytes()).await.map_err(|_| { + //error!("had error while writing: {:?}", err); + Error::Write + })?; + + if let Some(body) = body { + debug!("writing request data to socket"); + writer.write(body).await.map_err(|_| { + //error!("had error while writing: {:?}", err); + Error::Write + })?; + } + + writer.flush().await.map_err(|_| { + //error!("had error while flushing: {:?}", err); + Error::Flush + })?; + debug!("flushed socket"); + let mut header = [0u8; 16]; + debug!("reading response header"); + reader.read_exact(&mut header).await.map_err(|_| { + //error!("error while reading: {:?}", err); + Error::Read + })?; + + let header_response = Header(u128::from_be_bytes(header)); + match ( + header_response.message_class(), + header_response.message_type(), + header_response.content_type(), + ) { + (message_class, _, _) if message_class != MessageClass::Response as u128 => { + return Err(Error::InvalidMessageClass(message_class)); + } + (_, message_type_got, _) if message_type_got != message_type as u128 => { + return Err(Error::InvalidMessageType(message_type_got)); + } + (_, _, content_type_got) if content_type_got != content_type => { + return Err(Error::InvalidContentType(content_type_got)); + } + _ => {} + } + debug!("successfully extracted header"); + let content_len = header_response.content_len() as usize; + let mut header_status_response = Header(0); + if content_len > out.len() { + header_status_response.set_message_class(MessageClass::Error as u128); + writer + .write(&header_status_response.0.to_be_bytes()) + .await + .or(Err(Error::Write))?; + writer.flush().await.or(Err(Error::Flush))?; + return Err(Error::OutBufTooSmall { + required: content_len, + supplied: out.len(), + }); + } + header_status_response.set_message_class(MessageClass::Ok as u128); + writer + .write(&header_status_response.0.to_be_bytes()) + .await + .or(Err(Error::Write))?; + writer.flush().await.or(Err(Error::Flush))?; + debug!("successfully sent OK"); + debug!("attemtping to receive data"); + //let mut read_buf = [0u8; 1024]; + let mut bytes_received = 0; + loop { + let (from, to) = ( + bytes_received, + core::cmp::min(out.len(), bytes_received + 1024), + ); + let read_slice = &mut out[from..to]; + match reader.read(read_slice).await { + Ok(0) => { + warn!("read EOF"); + return Ok(bytes_received); + } + Ok(n) => { + debug!("read {} bytes", n); + bytes_received += n; + } + Err(_) => { + //warn!("read error: {:?}", e); + return Err(Error::Read); + } + } + } +} + +pub async fn fetch_file_flash( + mut reader: impl Read, + mut writer: impl Write, + mut out: impl NorFlash, + message_class: MessageClass, + message_type: MessageType, + content_type: u128, + header_data: Option, + body: Option<&[u8]>, +) -> Result<(usize, H), Error> { + let mut header = Header(0); + header.set_message_class(message_class as u128); + header.set_message_type(message_type as u128); + header.set_content_type(content_type); + if let Some(header_data) = header_data { + header.set_header_data(header_data); + } + header.set_content_len(body.map(|b| b.len()).unwrap_or_default() as u128); + debug!("content len: {}", header.content_len()); + + debug!("writing request header to socket"); + writer.write(&header.0.to_be_bytes()).await.map_err(|_| { + //error!("had error while writing: {:?}", err); + Error::Write + })?; + + if let Some(body) = body { + debug!("writing request data to socket"); + writer.write(body).await.map_err(|_| { + //error!("had error while writing: {:?}", err); + Error::Write + })?; + } + + writer.flush().await.map_err(|_| { + //error!("had error while flushing: {:?}", err); + Error::Flush + })?; + debug!("flushed socket"); + let mut header = [0u8; 16]; + debug!("reading response header"); + reader.read_exact(&mut header).await.map_err(|_| { + //error!("error while reading: {:?}", err); + Error::Read + })?; + + let header_response = Header(u128::from_be_bytes(header)); + match ( + header_response.message_class(), + header_response.message_type(), + header_response.content_type(), + ) { + (message_class, _, _) if message_class != MessageClass::Response as u128 => { + return Err(Error::InvalidMessageClass(message_class)); + } + (_, message_type_got, _) if message_type_got != message_type as u128 => { + return Err(Error::InvalidMessageType(message_type_got)); + } + (_, _, content_type_got) if content_type_got != content_type => { + return Err(Error::InvalidContentType(content_type_got)); + } + _ => {} + } + debug!("successfully extracted header"); + let content_len = header_response.content_len() as usize; + let mut header_status_response = Header(0); + if content_len > out.capacity() { + header_status_response.set_message_class(MessageClass::Error as u128); + writer + .write(&header_status_response.0.to_be_bytes()) + .await + .or(Err(Error::Write))?; + writer.flush().await.or(Err(Error::Flush))?; + return Err(Error::OutBufTooSmall { + required: content_len, + supplied: out.capacity(), + }); + } + header_status_response.set_message_class(MessageClass::Ok as u128); + writer + .write(&header_status_response.0.to_be_bytes()) + .await + .or(Err(Error::Write))?; + writer.flush().await.or(Err(Error::Flush))?; + + let mut bytes_received = 0; + let mut read_buf = [0u8; 1024]; + let mut hasher = H::new(); + loop { + match reader.read(&mut read_buf).await { + Ok(0) => { + warn!("read EOF"); + return Ok((bytes_received, hasher)); + } + Ok(n) => { + debug!("read {} bytes", n); + hasher.update(&read_buf[..n]); + out.write(bytes_received as u32, &read_buf[..n]) + .map_err(|_| Error::Write)?; + bytes_received += n; + } + Err(_) => { + //warn!("read error: {:?}", e); + return Err(Error::Read); + } + } + } +} + +pub async fn fetch_tuf_file( + reader: impl Read, + writer: impl Write, + out: &mut [u8], + metapath: &[u8], +) -> Result { + debug!( + "fetching tuf file at path {:?}", + core::str::from_utf8(metapath).unwrap_or("INVALID UTF-8 PATH") + ); + fetch_file( + reader, + writer, + out, + MessageClass::Request, + MessageType::Tuf, + TufMessage::TargetFile as u128, + None, + Some(metapath), + ) + .await +} + +pub async fn fetch_binary( + reader: impl Read, + writer: impl Write, + out: &mut [u8], + identifier: &[u8], +) -> Result { + debug!( + "fetching binary with identifier {:?}", + core::str::from_utf8(identifier).unwrap_or("INVALID UTF-8 PATH") + ); + fetch_file( + reader, + writer, + out, + MessageClass::Request, + MessageType::Binary, + 0, + None, + Some(identifier), + ) + .await +} + +pub async fn fetch_binary_flash( + reader: impl Read, + writer: impl Write, + out: impl NorFlash, + identifier: &[u8], +) -> Result<(usize, H), Error> { + debug!( + "fetching binary with identifier {:?}", + core::str::from_utf8(identifier).unwrap_or("INVALID UTF-8 PATH") + ); + fetch_file_flash( + reader, + writer, + out, + MessageClass::Request, + MessageType::Binary, + 0, + None, + Some(identifier), + ) + .await +} + +pub async fn fetch_signature( + reader: impl Read, + writer: impl Write, + out: &mut [u8], + identifier: &[u8], +) -> Result { + debug!( + "fetching binary with identifier {:?}", + core::str::from_utf8(identifier).unwrap_or("INVALID UTF-8 PATH") + ); + fetch_file( + reader, + writer, + out, + MessageClass::Request, + MessageType::Signature, + 0, + None, + Some(identifier), + ) + .await +} + +pub async fn send(mut reader: impl Read, mut writer: impl Write, data: &[u8]) -> Result<(), Error> { + let mut header_out = Header(0); + header_out.set_message_class(MessageClass::Response as u128); + header_out.set_message_type(MessageType::Tuf as u128); + header_out.set_content_type(TufMessage::Root as u128); + header_out.set_content_len(data.len() as u128); + + writer + .write(&header_out.0.to_be_bytes()) + .await + .or(Err(Error::Write))?; + writer.flush().await.or(Err(Error::Flush))?; + + let mut buf = [0u8; 16]; + reader.read_exact(&mut buf).await.or(Err(Error::Read))?; + let header_in = Header(u128::from_be_bytes(buf)); + if header_in.message_class() != MessageClass::Ok as u128 { + unimplemented!("handling error cases handling is not implemented") + } + + for bytes in data.chunks(1024) { + writer.write(bytes).await.or(Err(Error::Write))?; + writer.flush().await.or(Err(Error::Flush))?; + } + Ok(()) +} + +bitfield! { + pub struct Header(u128); + impl Debug; + pub message_class, set_message_class: 7, 0; + pub message_type, set_message_type : 15, 8; + pub content_type, set_content_type : 31, 16; + pub header_data, set_header_data : 63, 32; + pub content_len, set_content_len : 127, 64; +} + +#[cfg(test)] +mod test { + use alloc::borrow::ToOwned; + use alloc::vec; + use sha2::Sha256; + + use crate::{ + fetch_file_flash, fetch_tuf_file, fetch_tuf_role, send, Header, MessageClass, MessageType, + TufMessage, + }; + use embedded_storage::nor_flash::ReadNorFlash; + use tempfile::NamedTempFile; + + async fn test_send_impl(data: &str) { + let mut reader = vec![]; + + let mut header = Header(0); + header.set_message_class(MessageClass::Ok as u128); + reader.extend(header.0.to_be_bytes()); + + let mut writer = vec![0; data.len() + 16]; + + send(reader.as_slice(), writer.as_mut_slice(), data.as_bytes()) + .await + .expect("failed"); + eprintln!("{writer:?}"); + let header_got = Header(u128::from_be_bytes(writer[0..16].try_into().unwrap())); + + assert_eq!( + header_got.message_class(), + MessageClass::Response as u128, + "wrong message class" + ); + assert_eq!( + header_got.message_type(), + MessageType::Tuf as u128, + "wrong message type" + ); + assert_eq!( + header_got.content_type(), + TufMessage::Root as u128, + "wrong content type" + ); + let content_len = header_got.content_len() as usize; + assert_eq!(content_len, data.len()); + assert_eq!(std::str::from_utf8(&writer[16..]).unwrap(), data); + } + + #[tokio::test] + async fn test_send() { + test_send_impl("hello world").await; + } + + async fn test_fetch_impl(data: &str) { + let mut reader = vec![]; + let mut header = Header(0); + header.set_message_class(MessageClass::Response as u128); + header.set_message_type(MessageType::Tuf as u128); + header.set_content_type(TufMessage::Root as u128); + header.set_content_len(data.len() as u128); + reader.extend(header.0.to_be_bytes()); + reader.extend(data.bytes()); + let mut writer = [0u8; 1024]; + + let mut out_buf = [0u8; 1024]; + let n = fetch_tuf_role( + reader.as_slice(), + writer.as_mut_slice(), + &mut out_buf, + TufMessage::Root, + None, + ) + .await + .expect("failed"); + let first_header = Header(u128::from_be_bytes(writer[0..16].try_into().unwrap())); + let second_header = Header(u128::from_be_bytes(writer[16..32].try_into().unwrap())); + + eprintln!("{first_header:?}"); + assert_eq!( + first_header.message_class(), + MessageClass::Request as u128, + "wrong message class" + ); + assert_eq!( + first_header.message_type(), + MessageType::Tuf as u128, + "wrong message type" + ); + assert_eq!( + first_header.content_type(), + TufMessage::Root as u128, + "wrong content type" + ); + assert_eq!( + second_header.message_class(), + MessageClass::Ok as u128, + "wrong message class" + ); + assert_eq!(std::str::from_utf8(&out_buf[..n]).unwrap(), data); + } + + #[tokio::test] + async fn test_fetch() { + test_fetch_impl("hello world").await + } + + #[tokio::test] + async fn test_fetch_target_file() { + test_fetch_target_file_impl("hello.txt", "hello world").await + } + + async fn test_fetch_target_file_impl(path: &str, data: &str) { + let mut reader = vec![]; + let mut header = Header(0); + header.set_message_class(MessageClass::Response as u128); + header.set_message_type(MessageType::Tuf as u128); + header.set_content_type(TufMessage::TargetFile as u128); + header.set_content_len(data.len() as u128); + reader.extend(header.0.to_be_bytes()); + reader.extend(data.bytes()); + let mut writer = [0u8; 1024]; + + let mut out_buf = [0u8; 1024]; + let n = fetch_tuf_file( + reader.as_slice(), + writer.as_mut_slice(), + &mut out_buf, + path.as_bytes(), + ) + .await + .expect("failed"); + let first_header = Header(u128::from_be_bytes(writer[0..16].try_into().unwrap())); + let first_content = std::str::from_utf8(&writer[16..16 + path.len()]).unwrap(); + + let second_header = Header(u128::from_be_bytes( + writer[16 + path.len()..32 + path.len()].try_into().unwrap(), + )); + + eprintln!("{first_header:?}"); + assert_eq!( + first_header.message_class(), + MessageClass::Request as u128, + "wrong message class" + ); + assert_eq!( + first_header.message_type(), + MessageType::Tuf as u128, + "wrong message type" + ); + assert_eq!( + first_header.content_type(), + TufMessage::TargetFile as u128, + "wrong content type" + ); + assert_eq!( + first_header.content_len(), + path.len() as u128, + "wrong content len" + ); + assert_eq!(first_content, path, "wrong content"); + assert_eq!( + second_header.message_class(), + MessageClass::Ok as u128, + "wrong message class" + ); + assert_eq!(std::str::from_utf8(&out_buf[..n]).unwrap(), data); + } + + #[tokio::test] + async fn test_fetch_flash() { + let writer = vec![]; + let mut header = Header(0); + let file_content = b"Hello World!"; + header.set_message_class(MessageClass::Response as u128); + header.set_message_type(MessageType::Binary as u128); + header.set_header_data(file_content.len() as u128); + let reader = [header.0.to_be_bytes().as_slice(), file_content].concat(); + let file = dbg!(NamedTempFile::new().expect("failed to create tempfile")); + let mut flash_writer = crate::flash::mock_flash::FsWriter(file.path().to_owned()); + let path = b"hello-world.txt"; + fetch_file_flash::( + reader.as_slice(), + writer, + &mut flash_writer, + MessageClass::Request, + MessageType::Binary, + 0, + Some(path.len() as u128), + Some(path), + ) + .await + .expect("should succeed"); + let mut out_buf = [0u8; 12]; + flash_writer + .read(0, out_buf.as_mut_slice()) + .expect("failed to read bytes"); + assert_eq!(&out_buf, file_content); + } +} diff --git a/bt2x/bt2x-ota-common/src/net.rs b/bt2x/bt2x-ota-common/src/net.rs new file mode 100644 index 0000000..8f0ef8c --- /dev/null +++ b/bt2x/bt2x-ota-common/src/net.rs @@ -0,0 +1,184 @@ +use embassy_net::driver::Driver; +use embassy_net::tcp::TcpSocket; +use embassy_net::{IpEndpoint, Stack}; + +#[cfg(feature = "defmt")] +use defmt::error; +use embassy_time::Duration; +use embedded_storage::nor_flash::NorFlash; +#[cfg(feature = "log")] +use log::{debug, error, info, warn}; +use sha2::Digest; +#[cfg(feature = "tracing")] +use tracing::error; +use tuf_no_std::common::remote::{TransportError, TufTransportAsync}; + +use crate::TufMessage; + +/// Implementation for the networking functionality required for TUF and BT²X for embassy_net implementations. +pub struct Transport<'a, D: Driver> { + /// The address of the remote server. + pub server: IpEndpoint, + /// The network stack that is used to communicate with the remote host. + pub network_stack: &'a Stack, +} + +impl<'a, D: Driver> Transport<'a, D> { + /// Fetch the TUF role with the specified version number. + async fn fetch_role<'o>( + &self, + version: Option, + out: &'o mut [u8], + role: TufMessage, + ) -> Result<&'o [u8], TransportError> { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(self.network_stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + socket + .connect(self.server) + .await + .map_err(|_| TransportError::ConnectError)?; + let (reader, writer) = socket.split(); + let bytes_received = crate::fetch_tuf_role(reader, writer, out, role, version) + .await + .map_err(|err| { + error!("had error while fetching: {:?}", err); + TransportError::FetchError + })?; + socket.close(); + Ok(&out[..bytes_received]) + } +} + +impl<'a, D: Driver> TufTransportAsync for Transport<'a, D> { + async fn fetch_root<'o>( + &self, + version: core::num::NonZeroU64, + out: &'o mut [u8], + ) -> Result<&'o [u8], TransportError> { + self.fetch_role(Some(version), out, TufMessage::Root).await + } + + async fn fetch_timestamp<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::common::remote::TransportError> { + self.fetch_role(None, out, TufMessage::Timestamp).await + } + + async fn fetch_snapshot<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::common::remote::TransportError> { + self.fetch_role(None, out, TufMessage::Snapshot).await + } + + async fn fetch_targets<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::common::remote::TransportError> { + self.fetch_role(None, out, TufMessage::Targets).await + } + + async fn fetch_target_file<'o>( + &self, + metapath: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::common::remote::TransportError> { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(self.network_stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + socket + .connect(self.server) + .await + .map_err(|_| TransportError::ConnectError)?; + let (reader, writer) = socket.split(); + let bytes_received = crate::fetch_tuf_file(reader, writer, out, metapath) + .await + .map_err(|err| { + error!("had error while fetching: {:?}", err); + TransportError::FetchError + })?; + Ok(&out[..bytes_received]) + } +} + +impl<'a, D: Driver> Transport<'a, D> { + /// Fetch the binary with the given identifier and write it to memory. + /// This is only feasible for small binaries that fit into the RAM. + /// In most cases you should prefer the `fetch_binary_flash` function. + pub async fn fetch_binary<'o>( + &self, + identifier: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::common::remote::TransportError> { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(self.network_stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + socket + .connect(self.server) + .await + .map_err(|_| TransportError::ConnectError)?; + let (reader, writer) = socket.split(); + let bytes_received = crate::fetch_binary(reader, writer, out, identifier) + .await + .map_err(|err| { + error!("had error while fetching: {:?}", err); + TransportError::FetchError + })?; + Ok(&out[..bytes_received]) + } + + /// Fetch the binary with the given identifier and write it to the flash storage. + /// This also calculates a digest of the binary during download with the algorithm specified in `H`. + /// On success it returns the number of bytes that were written and the digest. + pub async fn fetch_binary_flash( + &self, + identifier: &[u8], + out: impl NorFlash, + ) -> Result<(usize, H), tuf_no_std::common::remote::TransportError> { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(self.network_stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + socket + .connect(self.server) + .await + .map_err(|_| TransportError::ConnectError)?; + let (reader, writer) = socket.split(); + let bytes_received = crate::fetch_binary_flash(reader, writer, out, identifier) + .await + .map_err(|err| { + error!("had error while fetching: {:?}", err); + TransportError::FetchError + })?; + Ok(bytes_received) + } + + /// Fetches the signature for the given identifier. + pub async fn fetch_signature<'o>( + &self, + identifier: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std::common::remote::TransportError> { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(self.network_stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + socket + .connect(self.server) + .await + .map_err(|_| TransportError::ConnectError)?; + let (reader, writer) = socket.split(); + let bytes_received = crate::fetch_signature(reader, writer, out, identifier) + .await + .map_err(|err| { + error!("had error while fetching: {:?}", err); + TransportError::FetchError + })?; + Ok(&out[..bytes_received]) + } +} diff --git a/bt2x/bt2x-ota/Cargo.toml b/bt2x/bt2x-ota/Cargo.toml new file mode 100644 index 0000000..089ce5d --- /dev/null +++ b/bt2x/bt2x-ota/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "bt2x-ota" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { workspace = true, features = ["derive"] } +bt2x-ota-common = { path = "../bt2x-ota-common", default-features = false, features = ["tracing"] } +tokio = { workspace = true, features = ["full"] } +anyhow = { workspace = true } +tracing = { workspace = true } +tracing-core = { workspace = true } +tracing-subscriber = { workspace = true } +path-clean = { workspace = true } diff --git a/bt2x/bt2x-ota/src/cli.rs b/bt2x/bt2x-ota/src/cli.rs new file mode 100644 index 0000000..ffa2bd7 --- /dev/null +++ b/bt2x/bt2x-ota/src/cli.rs @@ -0,0 +1,13 @@ +use clap::Parser; +use std::path::PathBuf; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + #[arg(long)] + pub repo_path: PathBuf, + #[arg(long)] + pub binaries: PathBuf, + #[arg(long)] + pub signatures: PathBuf, +} diff --git a/bt2x/bt2x-ota/src/main.rs b/bt2x/bt2x-ota/src/main.rs new file mode 100644 index 0000000..a118b3c --- /dev/null +++ b/bt2x/bt2x-ota/src/main.rs @@ -0,0 +1,329 @@ +mod cli; + +use crate::cli::Args; +use anyhow::{anyhow, Context, Result}; +use bt2x_ota_common::{Header, MessageClass, MessageType, TufMessage}; +use clap::Parser; +use path_clean::PathClean; +use std::error::Error; +use std::fs::OpenOptions; +use std::os::unix::fs::MetadataExt; +use std::path::PathBuf; +use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}; +use tokio::net::{TcpListener, TcpStream}; +use tracing::{debug, info}; +use tracing_subscriber::filter::Targets; +use tracing_subscriber::prelude::*; + +#[derive(Debug, Clone)] +struct Storage { + pub(crate) repository_path: PathBuf, + pub(crate) binaries_path: PathBuf, + pub(crate) signatures_path: PathBuf, +} + +impl Storage { + pub fn tuf_root(&self, version: u64) -> Result> { + info!("handling fetching root with version {version:?}"); + std::fs::read(self.repository_path.join(format!("{version}.root.der"))) + .context("failed to read file") + } + pub fn tuf_timestamp(&self, version: Option) -> Result> { + let version = version.unwrap_or(1); + info!("handling fetching timestamp with version {version:?}"); + std::fs::read( + self.repository_path + .join(format!("{version}.timestamp.der")), + ) + .context("failed to read file") + } + pub fn tuf_snapshot(&self, version: Option) -> Result> { + let version = version.unwrap_or(1); + info!("handling fetching snapshot with version {version:?}"); + std::fs::read(self.repository_path.join(format!("{version}.snapshot.der"))) + .context("failed to read file") + } + pub fn tuf_targets(&self, version: Option) -> Result> { + let version = version.unwrap_or(1); + info!("handling fetching targets with version {version:?}"); + std::fs::read(self.repository_path.join(format!("{version}.targets.der"))) + .context("failed to read file") + } + pub fn tuf_target(&self, metapath: &str) -> Result> { + info!("handling fetching target with path {metapath:?}"); + let metapath_clean = PathBuf::from(metapath).clean(); + let file_path = self.repository_path.join("targets").join(metapath_clean); + if !file_path.starts_with(&self.repository_path) { + return Err(anyhow!( + "requested path {file_path:?}is not within directory" + )); + } + std::fs::read(file_path).context("failed to read file") + } + + pub fn binary(&self, metapath: &str) -> Result<(u64, impl std::io::Read)> { + let metapath_clean = PathBuf::from(metapath).clean(); + OpenOptions::new() + .read(true) + .open(self.binaries_path.join(metapath_clean)) + .context("failed to open binary") + .map(|f| { + let size = f.metadata().as_ref().unwrap().size(); + (size, std::io::BufReader::new(f)) + }) + } + pub fn signature(&self, metapath: &str) -> Result<(u64, impl std::io::Read)> { + OpenOptions::new() + .read(true) + .open(self.signatures_path.join(metapath)) + .context("failed to open signature") + .map(|f| { + let size = f.metadata().as_ref().unwrap().size(); + (size, std::io::BufReader::new(f)) + }) + } +} + +async fn handle_client( + sock: &mut TcpStream, + storage: &Storage, +) -> Result<(), Box> { + let mut buf = [0u8; 16]; + sock.read_exact(&mut buf).await?; + let header = Header(u128::from_be_bytes(buf)); + debug!("got header: {header:?}"); + match (header.message_class(), header.message_type()) { + (c_class, m_type) + if (c_class, m_type) == (MessageClass::Request as u128, MessageType::Tuf as u128) => + { + debug!("handling tuf"); + handle_tuf(sock, header, storage).await? + } + + (c_class, m_type) + if (c_class, m_type) + == (MessageClass::Request as u128, MessageType::Binary as u128) => + { + debug!("handling binary"); + handle_binary(sock, header, storage).await? + } + (c_class, m_type) + if (c_class, m_type) + == ( + MessageClass::Request as u128, + MessageType::Signature as u128, + ) => + { + debug!("handling signature"); + handle_signature(sock, header, storage).await? + } + (c_class, m_type) + if (c_class, m_type) + == (MessageClass::Request as u128, MessageType::Rfc3161 as u128) => + { + debug!("handling tuf"); + handle_rfc3161(sock, header, storage).await? + } + _ => { + unimplemented!(); + } + } + Ok(()) +} + +async fn handle_tuf(stream: &mut TcpStream, header: Header, storage: &Storage) -> Result<()> { + let (data, role) = match header.content_type() { + c_type if c_type == TufMessage::Root as u128 => ( + storage.tuf_root(header.header_data() as u64)?, + TufMessage::Root, + ), + c_type if c_type == TufMessage::Targets as u128 => { + (storage.tuf_targets(None)?, TufMessage::Targets) + } + c_type if c_type == TufMessage::Snapshot as u128 => { + (storage.tuf_snapshot(None)?, TufMessage::Snapshot) + } + c_type if c_type == TufMessage::Timestamp as u128 => { + (storage.tuf_timestamp(None)?, TufMessage::Timestamp) + } + c_type if c_type == TufMessage::TargetFile as u128 => { + let mut buf = vec![0; header.content_len() as usize]; + stream + .read_exact(&mut buf) + .await + .context("could not read path from socket")?; + debug!("{buf:x?}"); + let path = std::str::from_utf8(&buf).context("provided path was not valid UTF-8")?; + debug!("got path: {path:?}"); + (storage.tuf_target(path)?, TufMessage::TargetFile) + } + _ => unimplemented!(), + }; + debug!("handling {role:?}"); + send(stream, &data, role).await +} + +async fn handle_signature(stream: &mut TcpStream, header: Header, storage: &Storage) -> Result<()> { + let (read, write) = stream.split(); + let mut reader = BufReader::new(read); + let mut writer = BufWriter::new(write); + + let mut path = [0u8; 256]; + let content_len = header.content_len() as usize; + reader.read_exact(&mut path[..content_len]).await?; + let path = std::str::from_utf8(&path[..content_len])?; + let (data_len, mut data) = storage.signature(path)?; + + let mut header_out = Header(0); + header_out.set_message_class(MessageClass::Response as u128); + header_out.set_message_type(MessageType::Signature as u128); + header_out.set_content_len(data_len as u128); + + writer.write_all(&header_out.0.to_be_bytes()).await?; + writer.flush().await?; + info!("sent header"); + let mut buf = [0u8; 16]; + reader.read_exact(&mut buf).await?; + let header_in = Header(u128::from_be_bytes(buf)); + if header_in.message_class() != MessageClass::Ok as u128 { + return Err(anyhow!("got message != Ok from remote")); + } + info!("sending data"); + loop { + use std::io::Read; + let mut buf = [0u8; 1024]; + let bytes_read = data.read(&mut buf)?; + if bytes_read == 0 { + break; + } + let mut buf = &buf[..bytes_read]; + debug!("sending {} bytes", buf.len()); + loop { + if buf.is_empty() { + break; + } + let bytes_sent = writer.write(buf).await?; + debug!("sent {bytes_sent} bytes"); + buf = &buf[bytes_sent..]; + writer.flush().await?; + } + } + info!("sending data finished"); + Ok(()) +} + +async fn handle_binary(stream: &mut TcpStream, header: Header, storage: &Storage) -> Result<()> { + let (read, write) = stream.split(); + let mut reader = BufReader::new(read); + let mut writer = BufWriter::new(write); + + let mut path = [0u8; 256]; + let content_len = header.content_len() as usize; + reader.read_exact(&mut path[..content_len]).await?; + let path = std::str::from_utf8(&path[..content_len])?; + info!("client requested binary at: {path}"); + let (data_len, mut data) = storage.binary(path)?; + + let mut header_out = Header(0); + header_out.set_message_class(MessageClass::Response as u128); + header_out.set_message_type(MessageType::Binary as u128); + header_out.set_content_len(data_len as u128); + + writer.write_all(&header_out.0.to_be_bytes()).await?; + writer.flush().await?; + info!("sent header"); + let mut buf = [0u8; 16]; + reader.read_exact(&mut buf).await?; + let header_in = Header(u128::from_be_bytes(buf)); + if header_in.message_class() != MessageClass::Ok as u128 { + return Err(anyhow!("got message != Ok from remote")); + } + info!("sending data"); + loop { + use std::io::Read; + let mut buf = [0u8; 1024]; + let bytes_read = data.read(&mut buf)?; + if bytes_read == 0 { + break; + } + let mut buf = &buf[..bytes_read]; + debug!("sending {} bytes", buf.len()); + loop { + if buf.is_empty() { + break; + } + let bytes_sent = writer.write(buf).await?; + debug!("sent {bytes_sent} bytes"); + buf = &buf[bytes_sent..]; + writer.flush().await?; + } + } + info!("sending data finished"); + Ok(()) +} + +#[allow(unused_variables)] +async fn handle_rfc3161(stream: &mut TcpStream, header: Header, storage: &Storage) -> Result<()> { + unimplemented!() +} + +async fn send(stream: &mut TcpStream, data: &[u8], role: TufMessage) -> Result<()> { + let mut header_out = Header(0); + header_out.set_message_class(MessageClass::Response as u128); + header_out.set_message_type(MessageType::Tuf as u128); + header_out.set_content_type(role as u128); + header_out.set_content_len(data.len() as u128); + let (read, write) = stream.split(); + let mut reader = BufReader::new(read); + let mut writer = BufWriter::new(write); + writer.write_all(&header_out.0.to_be_bytes()).await?; + writer.flush().await?; + info!("sent header"); + let mut buf = [0u8; 16]; + reader.read_exact(&mut buf).await?; + let header_in = Header(u128::from_be_bytes(buf)); + if header_in.message_class() != MessageClass::Ok as u128 { + return Err(anyhow!("got message != Ok from remote")); + } + info!("sending data"); + for bytes in data.chunks(1024) { + debug!("sending {} bytes", bytes.len()); + let n = writer.write(bytes).await?; + writer.flush().await?; + debug!("sent {n} bytes"); + } + info!("sending data finished"); + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(Targets::new().with_target( + env!("CARGO_PKG_NAME").replace('-', "_"), + tracing_core::Level::INFO, + )) + .init(); + let args = Args::parse(); + + let storage = Storage { + repository_path: args.repo_path, + signatures_path: args.signatures, + binaries_path: args.binaries, + }; + debug!("starting TCP listener"); + let sockaddr = ("0.0.0.0", 50000); + let listener = TcpListener::bind(sockaddr) + .await + .expect("failed to bind to socket"); + debug!("successfully bound to {sockaddr:?}"); + loop { + let (mut socket, addr) = listener.accept().await?; + debug!("got client: {addr:?}"); + match handle_client(&mut socket, &storage).await { + Ok(_) => continue, + Err(err) => println!("encountered error: {err:?}"), + } + } +} diff --git a/bt2x/bt2x-server/Cargo.toml b/bt2x/bt2x-server/Cargo.toml new file mode 100644 index 0000000..57e9a1a --- /dev/null +++ b/bt2x/bt2x-server/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bt2x-server" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bt2x-common = { path = "../bt2x-common" } +sigstore = { workspace = true } +tokio = { workspace = true, features = ["full"] } +futures = { workspace = true } +clap = { workspace = true, features = ["derive"] } +oci-distribution = { workspace = true, features = ["rustls-tls"] } +url = { workspace = true } +tracing-subscriber = { workspace = true, features = [ + "fmt", + "std", + "env-filter", +] } +tracing = { workspace = true } +tracing-core = { workspace = true } +humantime = { workspace = true } +serde = { workspace = true } +anyhow = { workspace = true } +serde_yaml = { workspace = true } +serde_json = { workspace = true } +base64 = { workspace = true } +rustls-pki-types = { workspace = true } +rustls-pemfile = { workspace = true } diff --git a/bt2x/bt2x-server/src/cli.rs b/bt2x/bt2x-server/src/cli.rs new file mode 100644 index 0000000..a8ca93e --- /dev/null +++ b/bt2x/bt2x-server/src/cli.rs @@ -0,0 +1,53 @@ +use clap::Parser; +use sigstore::registry::OciReference; +use std::path::PathBuf; + +#[derive(Parser, Debug, Clone)] +#[command(author, version, about, long_about = None)] +pub(crate) struct Args { + /// OCI image references + #[arg(long, value_name = "OCI REFERENCE")] + pub(crate) image: Vec, + #[arg(long)] + pub(crate) fulcio_url: Option, + #[arg(long)] + pub(crate) rekor_url: Option, + #[arg(long)] + pub(crate) ct_log_url: Option, + /// allow HTTP connections + #[clap(long)] + pub(crate) http: bool, + /// application logging level + #[clap(long, value_enum, default_value_t = LogLevel::Info)] + pub(crate) log_level: LogLevel, + /// interval at which OCI images are checked + #[clap(long)] + pub(crate) interval: Option, + /// path to the configuration file, consult [crate::config::Config] for more information. + #[clap(long, value_name = "PATH")] + pub(crate) config: Option, + /// directory to which audited files and signatures are written to + #[clap(long)] + pub(crate) outdir: PathBuf, +} + +#[derive(clap::ValueEnum, Clone, Debug)] +pub(crate) enum LogLevel { + Debug, + Info, + Warn, + Error, + Trace, +} + +impl From<&LogLevel> for tracing_core::LevelFilter { + fn from(value: &LogLevel) -> Self { + match value { + LogLevel::Debug => tracing_core::Level::DEBUG.into(), + LogLevel::Info => tracing_core::Level::INFO.into(), + LogLevel::Warn => tracing_core::Level::WARN.into(), + LogLevel::Error => tracing_core::Level::ERROR.into(), + LogLevel::Trace => tracing_core::Level::TRACE.into(), + } + } +} diff --git a/bt2x/bt2x-server/src/config.rs b/bt2x/bt2x-server/src/config.rs new file mode 100644 index 0000000..34be648 --- /dev/null +++ b/bt2x/bt2x-server/src/config.rs @@ -0,0 +1,83 @@ +use bt2x_common::sigstore_config::SigstoreConfig; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use sigstore::cosign::verification_constraint::{ + CertSubjectEmailVerifier, CertSubjectUrlVerifier, VerificationConstraint, +}; +use sigstore::registry::OciReference; +use std::collections::HashMap; +use std::str::FromStr; +use url::Url; + +/// Data structure to configure this application. +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct Config { + /// list of [[References]] to OCI images that will be monitored + pub references: Vec, + /// Sigstore configuration, for more details refer to [[bt2x_common::sigstore_config::SigstoreConfig]] + pub sigstore_config: SigstoreConfig, + /// URLs of monitors to which gossip is sent + pub monitors: Vec, +} + +/// function to handle [[serde]] serialization for OCI references +fn serialize_oci_ref(reference: &OciReference, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(reference.whole().as_str()) +} + +/// function to handle [[serde]] deserialization for OCI references +fn deserialize_oci_ref<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let buf = String::deserialize(deserializer)?; + + OciReference::from_str(&buf).map_err(serde::de::Error::custom) +} + +/// data structure to configure images that will be audited/verified +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct References { + /// tag that is used to identify the image + #[serde( + serialize_with = "serialize_oci_ref", + deserialize_with = "deserialize_oci_ref" + )] + pub tag: OciReference, + /// subject identities that are trusted as signers for this image + pub subjects: Option>, + /// optional: annotations are are verified on this image + pub annotations: Option>, +} + +/// This enum can be used to configure the subject that signed the artifact. +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Subject { + /// the subject is identified by an (E-Mail, OIDC issuer) tuple. + Email { + email: String, + /// The issuer, this is an `Option` for compatibility with the Sigstore library. + /// However, leaving the issuer unspecified is probably not desirable in case the same E-Mail is registered with multiple issuers. + issuer: Option, + }, + /// The subject is identified by a (URL, OIDC issuer) tuple. + Url { url: url::Url, issuer: url::Url }, +} + +impl From for Box { + fn from(value: Subject) -> Self { + match value { + Subject::Email { email, issuer } => Box::new(CertSubjectEmailVerifier { + email, + issuer: issuer.map(|url| url.to_string()), + }), + Subject::Url { url, issuer } => Box::new(CertSubjectUrlVerifier { + url: url.to_string(), + issuer: issuer.to_string(), + }), + } + } +} diff --git a/bt2x/bt2x-server/src/main.rs b/bt2x/bt2x-server/src/main.rs new file mode 100644 index 0000000..76e5024 --- /dev/null +++ b/bt2x/bt2x-server/src/main.rs @@ -0,0 +1,328 @@ +mod cli; +mod config; + +use sigstore::registry::{ClientConfig, ClientProtocol, OciReference}; +use sigstore::trust::ManualTrustRoot; +use std::collections::HashMap; +use url::Url; + +use bt2x_common::rekor::Configuration as RekorConfig; +use bt2x_common::rekor::RekorClient; +use clap::Parser; +use sigstore::cosign::verification_constraint::{AnnotationVerifier, VerificationConstraintVec}; +use std::fs; +use std::fs::{read_to_string, OpenOptions}; +use std::io::{BufReader, Cursor, Write}; +use std::path::{Path, PathBuf}; + +use oci_distribution::client as oci_client; +use oci_distribution::secrets::RegistryAuth; +use tracing::{debug, error, info}; + +use tracing_subscriber::filter::Targets; + +use crate::config::References; +use anyhow::{anyhow, Result}; +use bt2x_common::artifact::source::oci::OciSource; +use bt2x_common::verifier::bt::{BinaryTransparencyVerifier, SigstoreKeys}; +use bt2x_common::verifier::Artifact::{Binary, BundledBinary}; +use bt2x_common::verifier::{Artifact, Verifier}; +use sigstore::cosign::bundle::SignedArtifactBundle; +use tracing_subscriber::prelude::*; + +use base64::engine::{general_purpose::STANDARD as BASE64_STANDARD, Engine}; +use bt2x_common::sigstore_config::KeyConfig; +use bt2x_common::tuf::load_tuf_filesystem; +use rustls_pemfile::{read_one, Item}; + +fn configure_logging(cli: &cli::Args) { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with( + Targets::new() + .with_target(env!("CARGO_PKG_NAME").replace('-', "_"), &cli.log_level) + .with_target("bt2x_common", &cli.log_level) + .with_target("sigstore", tracing_core::Level::INFO) + .with_target("oci_distribution", tracing_core::Level::INFO) + .with_target("hyper", tracing_core::Level::INFO) + .with_target("reqwest", tracing_core::Level::INFO), + ) + .init(); +} + +#[tokio::main] +pub async fn main() { + // parse args and read config + let cli = crate::cli::Args::parse(); + let config = cli + .config + .as_ref() + .map(|path| read_to_string(path).expect("config file not found")) + .map(|s| serde_yaml::from_str::(&s).expect("invalid config file")) + .unwrap(); + configure_logging(&cli.clone()); + + // either load keys from filesystem or load them from a TUF repo stored in the filesystem + let (rekor_key, fulcio_cert, ctlog_key) = match config.sigstore_config.key_config { + KeyConfig::Tuf { + metadata_base, + targets_base, + root_path, + target_names, + } => { + let repo = + load_tuf_filesystem(&root_path, &metadata_base, &targets_base, &target_names) + .await + .expect(""); + (repo.rekor_key, repo.fulcio_cert, repo.ctlog_key) + } + KeyConfig::Keys { + rekor_key: Some(rekor_key), + ctlog_key: Some(ctlog_key), + fulcio_cert: Some(fulcio_cert), + } => ( + fs::read(rekor_key).expect("failed to read rekor key"), + fs::read(fulcio_cert).expect("failed to read fulcio cert"), + fs::read(ctlog_key).expect("failed to read rekor key"), + ), + _ => panic!("rekor key required, update config"), + }; + + // setup clients and configs for clients + let rekor_client = RekorClient::new(RekorConfig { + base_path: config.sigstore_config.urls.rekor.to_string(), + ..Default::default() + }); + + let registry_config = ClientConfig { + protocol: if cli.http { + ClientProtocol::Http + } else { + ClientProtocol::Https + }, + accept_invalid_certificates: false, + extra_root_certificates: vec![], + accept_invalid_hostnames: true, + }; + let mut reader = BufReader::new(Cursor::new(fulcio_cert.clone())); + let Some(Item::X509Certificate(fulcio_cert_der)) = + read_one(&mut reader).expect("failed to decode PEM") + else { + panic!("error converting parsing fulcio cert") + }; + let mut reader = BufReader::new(Cursor::new(rekor_key.clone())); + let Some(Item::SubjectPublicKeyInfo(rekor_key_der)) = + read_one(&mut reader).expect("failed to decode PEM") + else { + panic!("error converting parsing rekor key") + }; + let mut reader = BufReader::new(Cursor::new(ctlog_key.clone())); + let Some(Item::SubjectPublicKeyInfo(ctlog_key_der)) = + read_one(&mut reader).expect("failed to decode PEM") + else { + panic!("error converting parsing the CT log key") + }; + + let cosign_client = sigstore::cosign::ClientBuilder::default() + .with_trust_repository(&ManualTrustRoot { + ctfe_keys: vec![ctlog_key_der.to_vec()], + rekor_keys: vec![rekor_key_der.to_vec()], + fulcio_certs: vec![fulcio_cert_der], + }) + .expect("failed to add trust repo") + .with_oci_client_config(registry_config) + .build() + .expect("Unexpected failure while building Client"); + + let oci_client_config = oci_client::ClientConfig { + protocol: if cli.http { + oci_client::ClientProtocol::Http + } else { + oci_client::ClientProtocol::Https + }, + ..Default::default() + }; + + // configure verifier + let mut verifier = BinaryTransparencyVerifier::builder() + .monitors(config.monitors) + .rekor_client(rekor_client) + .cosign_client(cosign_client) + .keys( + SigstoreKeys::builder() + .rekor_pub_key(String::from_utf8(rekor_key.to_vec()).unwrap()) + .fulcio_cert(String::from_utf8(fulcio_cert.to_vec()).unwrap()) + .ct_log_key(String::from_utf8(fulcio_cert.to_vec()).unwrap()) + .build(), + ) + .build(); + + // configure source from which images are downloaded + let mut oci_source = OciSource::new(oci_client_config, RegistryAuth::Anonymous); + let references: Vec<(OciReference, VerificationConstraintVec)> = config + .references + .into_iter() + .chain(cli.image.into_iter().map(|r| References { + tag: r, + subjects: None, + annotations: None, + })) + .map(|r| { + let mut constraints = VerificationConstraintVec::new(); + constraints.extend(r.subjects.unwrap_or_default().into_iter().map(|s| s.into())); + constraints.push(Box::new(AnnotationVerifier { + annotations: r.annotations.unwrap_or_default(), + })); + (r.tag, constraints) + }) + .collect(); + debug!("{:#?}", references); + + // server loop + loop { + debug!("running loop"); + // loop over references + for (reference, constraints) in references.iter() { + debug!("checking {}", &reference); + // verify that each reference is valid under the given constraints + // on success it writes the image to the file system + match run_verify(&mut oci_source, reference, constraints, &mut verifier).await { + // raw binary + Ok(Binary(artifact)) => { + info!("Image successfully verified"); + let bytes = artifact; + fs::create_dir_all(&cli.outdir).expect("failed to create output dir"); + + let out_path = match build_path(cli.outdir.as_path(), reference) { + Ok(path) => path, + Err(err) => { + error!( + "could not create path for {reference:?} output because of {err:?}" + ); + continue; + } + }; + debug!("Path is {out_path:?}"); + if !out_path.exists() { + debug!("Writing to {out_path:?}"); + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(out_path) + .expect("failed to create output file") + .write_all(&bytes) + .expect("failed to write output file"); + } + } + Err(err) => { + info!("{:?}", err); + continue; + } + // binary that is bundled with a signature + Ok(BundledBinary { binary, bundle }) => { + info!("Image AND Bundle successfully verified"); + let bytes = binary; + fs::create_dir_all(&cli.outdir).expect("failed to create output dir"); + + let out_path = match build_path(cli.outdir.as_path(), reference) { + Ok(path) => path, + Err(err) => { + error!( + "could not create path for {reference:?} output because of {err:?}" + ); + continue; + } + }; + debug!("Path is {out_path:?}"); + debug!("Writing to {out_path:?}"); + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&out_path) + .expect("failed to create output file") + .write_all(&bytes) + .expect("failed to write output file"); + let mut out_path_bundle = out_path.clone(); + out_path_bundle.set_extension("json"); + debug!("Bundle path is {out_path_bundle:?}"); + debug!("Writing to {out_path_bundle:?}"); + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(out_path_bundle) + .expect("failed to create output file") + .write_all(&bundle) + .expect("failed to write output file"); + let mut out_path_bundle = out_path.clone(); + out_path_bundle.set_extension("canonical.json"); + debug!("Bundle path is {out_path_bundle:?}"); + let bundle: SignedArtifactBundle = + serde_json::from_slice(bundle.as_slice()).expect("failed to parse bundle"); + let canonical_json = BASE64_STANDARD + .encode(bundle.rekor_bundle.payload.to_canonical_json().unwrap()); + let encoded_set = BASE64_STANDARD + .encode(bundle.rekor_bundle.signed_entry_timestamp.as_slice()); + let canonical_bundle = HashMap::from([ + ("SignedEntryTimestamp", &encoded_set), + ("Payload", &canonical_json), + ]); + debug!("Writing to {out_path_bundle:?}"); + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(out_path_bundle) + .expect("failed to create output file") + .write_all(&serde_json::to_vec(&canonical_bundle).unwrap()) + .expect("failed to write output file"); + } + } + } + + if let Some(interval) = cli.interval { + tokio::time::sleep(interval.into()).await + } else { + debug!("Exiting..."); + break; + } + } +} + +/// Builds a file path from the OCI reference at which the output file is stored. +fn build_path(base_path: &Path, reference: &OciReference) -> Result { + let out_path = PathBuf::from(reference.repository()); + let out_path = format!( + "{}-{}.{}", + out_path + .file_stem() + .ok_or(anyhow!("could not create file name from reference")) + .and_then(|s| s + .to_str() + .ok_or(anyhow!("could not get unicode string from path")))?, + reference + .digest() + .or(reference.tag()) + .ok_or(anyhow!("OCI reference requires tag or digest")) + .map(|s| s.trim_start_matches("sha256:"))?, + out_path + .extension() + .unwrap_or("".as_ref()) + .to_str() + .unwrap() + ); + let out_path = out_path.trim_start_matches('/').trim_end_matches('.'); + Ok(base_path.join(out_path)) +} + +async fn run_verify( + oci_source: &mut OciSource, + reference: &OciReference, + constraints: &VerificationConstraintVec, + verifier: &mut BinaryTransparencyVerifier, +) -> Result>> { + debug!("checking {}", &reference); + verifier.verify(oci_source, reference, constraints).await +} diff --git a/bt2x/deny.toml b/bt2x/deny.toml new file mode 100644 index 0000000..5f4e24e --- /dev/null +++ b/bt2x/deny.toml @@ -0,0 +1,230 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "MPL-2.0", + "ISC", + "Unicode-DFS-2016", + "BSD-3-Clause", + "0BSD", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + # { allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +crate = "ring" +expression = "MIT AND ISC AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = ["embassy-rs"] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/bt2x/rust-toolchain.toml b/bt2x/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/bt2x/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/build-docs.sh b/build-docs.sh new file mode 100755 index 0000000..4453f6e --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,208 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo "Building docs for tuf-no-std" +cd tuf-no-std && cargo doc --open --no-deps && cd .. + +echo "Building docs for BT2X" +cd bt2x && cargo doc --open --no-deps && cd .. diff --git a/configs/bt2x-monitor/sigstore-config.yaml b/configs/bt2x-monitor/sigstore-config.yaml new file mode 100644 index 0000000..bd0debd --- /dev/null +++ b/configs/bt2x-monitor/sigstore-config.yaml @@ -0,0 +1,9 @@ +key_config: + !keys + rekor_key: "/etc/monitor/rekor_key.pub" + fulcio_cert: "" + ctlog_key: "" +urls: + rekor: http://rekor:3000 + fulcio: http://fulcio:5555 + oidc_issuer: http://dex-idp:8888/ \ No newline at end of file diff --git a/configs/bt2x-server/config.yaml b/configs/bt2x-server/config.yaml new file mode 100644 index 0000000..15e4b9c --- /dev/null +++ b/configs/bt2x-server/config.yaml @@ -0,0 +1,17 @@ +references: + - tag: container-registry:1338/bt2x/pi-pico:bin + subjects: + - email: add@github-email.here + issuer: http://dex-idp:8888/ +sigstore_config: + key_config: + !keys + rekor_key: "/tuf-repo/targets/rekor.pub" + fulcio_cert: "/tuf-repo/targets/fulcio.crt.pem" + ctlog_key: "/tuf-repo/targets/ctlog.pub" + urls: + rekor: http://rekor:3000 + fulcio: http://fulcio:5555 + oidc_issuer: http://dex-idp:8888/ +monitors: + - http://bt2x-monitor-1:3132 \ No newline at end of file diff --git a/configs/dex/dex.yaml b/configs/dex/dex.yaml new file mode 100644 index 0000000..565b5f1 --- /dev/null +++ b/configs/dex/dex.yaml @@ -0,0 +1,55 @@ +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +issuer: http://dex-idp:8888/ + +storage: + type: memory + +web: + http: 0.0.0.0:8888 + +frontend: + issuer: Fulcio in Docker Compose + +expiry: + signingKeys: "24h" + idTokens: "1m" + authRequests: "24h" + +oauth2: + responseTypes: [ "code" ] + alwaysShowLoginScreen: true + skipApprovalScreen: true + +connectors: + - type: mockCallback + id: https://any.valid.url/ + name: AlwaysApprovesOIDCProvider + - type: github + id: github-sigstore-test + name: GitHub + config: + clientID: $GITHUB_CLIENT_ID + clientSecret: $GITHUB_CLIENT_SECRET + redirectURI: http://dex-idp:8888/callback + +staticClients: + - id: sigstore + public: true + name: 'Fulcio in Docker Compose' + +# Dex's issuer URL + "/callback" +redirectURI: http://dex-idp:8888/callback diff --git a/configs/sigstore/fulcio/config/cert-gen.sh b/configs/sigstore/fulcio/config/cert-gen.sh new file mode 100755 index 0000000..6233d2e --- /dev/null +++ b/configs/sigstore/fulcio/config/cert-gen.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +echo "Generating key..." +openssl ecparam -genkey \ + -name prime256v1 \ + -noout \ + -out fulcio_key_unencrypted.pem +echo "Completed." +echo "Generating encrypted private key..." +openssl ec \ + -in fulcio_key_unencrypted.pem \ + -out fulcio_key.pem -aes256 +echo "Generating CA Certificate..." +openssl req -x509 \ + -key fulcio_key_unencrypted.pem \ + -out fulcio.pem -sha256 \ + -days 365 \ + -subj "/C=DE/ST=Bayern/L=Garching bei Muenchen/O=bt2x/CN=fulcio" \ + -addext basicConstraints=critical,CA:TRUE,pathlen:1 \ + -addext keyUsage=critical,keyCertSign,cRLSign +chmod o+r fulcio_key.pem +echo "Completed." diff --git a/configs/sigstore/fulcio/config/config.json b/configs/sigstore/fulcio/config/config.json new file mode 100644 index 0000000..821252e --- /dev/null +++ b/configs/sigstore/fulcio/config/config.json @@ -0,0 +1,9 @@ +{ + "OIDCIssuers": { + "http://dex-idp:8888/": { + "IssuerURL": "http://dex-idp:8888/", + "ClientID": "sigstore", + "Type": "email" + } + } +} \ No newline at end of file diff --git a/configs/sigstore/fulcio/config/dex/docker-compose-config.yaml b/configs/sigstore/fulcio/config/dex/docker-compose-config.yaml new file mode 100644 index 0000000..3865532 --- /dev/null +++ b/configs/sigstore/fulcio/config/dex/docker-compose-config.yaml @@ -0,0 +1,55 @@ +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +issuer: http://dex-idp:8888/auth + +storage: + type: memory + +web: + http: 0.0.0.0:8888 + +frontend: + issuer: Fulcio in Docker Compose + +expiry: + signingKeys: "24h" + idTokens: "1m" + authRequests: "24h" + +oauth2: + responseTypes: [ "code" ] + alwaysShowLoginScreen: true + skipApprovalScreen: true + +connectors: +- type: mockCallback + id: https://any.valid.url/ + name: AlwaysApprovesOIDCProvider +- type: github + id: github-sigstore-test + name: GitHub + config: + clientID: $GITHUB_CLIENT_ID + clientSecret: $GITHUB_CLIENT_SECRET + redirectURI: http://localhost:5556/callback + +staticClients: + - id: fulcio + public: true + name: 'Fulcio in Docker Compose' + +# Dex's issuer URL + "/callback" +redirectURI: http://dex-idp:8888/auth/callback diff --git a/configs/sigstore/fulcio/config/fulcio-config.yaml b/configs/sigstore/fulcio/config/fulcio-config.yaml new file mode 100644 index 0000000..097e301 --- /dev/null +++ b/configs/sigstore/fulcio/config/fulcio-config.yaml @@ -0,0 +1,75 @@ +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: v1 +data: + config.json: |- + { + "OIDCIssuers": { + "https://accounts.google.com": { + "IssuerURL": "https://accounts.google.com", + "ClientID": "sigstore", + "Type": "email" + }, + "https://allow.pub": { + "IssuerURL": "https://allow.pub", + "ClientID": "sigstore", + "Type": "spiffe", + "SPIFFETrustDomain": "allow.pub" + }, + "https://oauth2.sigstore.dev/auth": { + "IssuerURL": "https://oauth2.sigstore.dev/auth", + "ClientID": "sigstore", + "Type": "email", + "IssuerClaim": "$.federated_claims.connector_id" + }, + "https://oidc.dlorenc.dev": { + "IssuerURL": "https://oidc.dlorenc.dev", + "ClientID": "sigstore", + "Type": "spiffe", + "SPIFFETrustDomain": "oidc.dlorenc.dev" + }, + "https://token.actions.githubusercontent.com": { + "IssuerURL": "https://token.actions.githubusercontent.com", + "ClientID": "sigstore", + "Type": "github-workflow" + } + }, + "MetaIssuers": { + "https://container.googleapis.com/v1/projects/*/locations/*/clusters/*": { + "ClientID": "sigstore", + "Type": "kubernetes" + }, + "https://oidc.eks.*.amazonaws.com/id/*": { + "ClientID": "sigstore", + "Type": "kubernetes" + }, + "https://oidc.prod-aks.azure.com/*": { + "ClientID": "sigstore", + "Type": "kubernetes" + } + } + } + server.yaml: |- + host: 0.0.0.0 + port: 5555 + grpc-port: 5554 + ca: googleca + ct-log-url: http://ct-log/test + log_type: prod + metrics-port: 2113 +kind: ConfigMap +metadata: + name: fulcio-config + namespace: fulcio-system diff --git a/configs/sigstore/fulcio/ctfe-init/Dockerfile b/configs/sigstore/fulcio/ctfe-init/Dockerfile new file mode 100644 index 0000000..e9f05cd --- /dev/null +++ b/configs/sigstore/fulcio/ctfe-init/Dockerfile @@ -0,0 +1,25 @@ +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang:1.19.2@sha256:25de7b6b28219279a409961158c547aadd0960cf2dcbc533780224afa1157fd4 AS builder + +WORKDIR /root/ + +RUN go install github.com/google/trillian/cmd/createtree@v1.3.10 +ADD ./logid.sh /root/ +ADD ./config /root/ctfe +RUN chmod +x /root/logid.sh + +CMD /root/logid.sh diff --git a/configs/sigstore/fulcio/ctfe-init/config/ct_server.cfg b/configs/sigstore/fulcio/ctfe-init/config/ct_server.cfg new file mode 100644 index 0000000..1f33b6a --- /dev/null +++ b/configs/sigstore/fulcio/ctfe-init/config/ct_server.cfg @@ -0,0 +1,12 @@ +config { + log_id: %LOGID% + prefix: "sigstore" + roots_pem_file: "/etc/config/root.pem" + private_key: { + [type.googleapis.com/keyspb.PEMKeyFile] { + path: "/etc/config/privkey.pem" + password: "foobar" + } + } + ext_key_usages: [ "CodeSigning" ] +} diff --git a/configs/sigstore/fulcio/ctfe-init/logid.sh b/configs/sigstore/fulcio/ctfe-init/logid.sh new file mode 100644 index 0000000..e0a0cff --- /dev/null +++ b/configs/sigstore/fulcio/ctfe-init/logid.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +function get_log_id() { + curl -s --retry-connrefused --retry 10 http://ct-trillian-log-server:8091/metrics |grep "^quota_acquired_tokens{spec=\"trees"|head -1|awk ' { print $1 } '|sed -e 's/[^0-9]*//g' > /tmp/logid +} + +function create_log () { + /go/bin/createtree -admin_server ct-trillian-log-server:8090 > /tmp/logid + echo -n "Created log ID " && cat /tmp/logid +} + +function update_config() { + cat /root/ctfe/ct_server.cfg | sed -e "s/%LOGID%/"`cat /tmp/logid`"/g" > /etc/config/ct_server.cfg + cp /root/ctfe/*.pem /etc/config/ +} + +# check to see if log id exists; if so, use that +echo -n "Checking for existing configuration..." +if ! [[ -s /etc/config/ct_server.cfg ]]; then + echo " none found." + echo "Checking for preexisting logs..." + get_log_id + # else create one + if ! [[ -s /tmp/logid ]]; then + echo "No log found; let's create one..." + create_log + # update config file accordingly + update_config + else + echo "Log ID known but config not found" + update_config + fi +else + echo " found." + configid=`cat /etc/config/ct_server.cfg|grep log_id|awk ' { print $2 } '` + echo "Existing configuration uses log ID $configid" +fi +curl -s --retry-connrefused --retry 10 http://fulcio:5555/api/v1/rootCert -o tmpchain.pem +csplit -s -f tmpcert- tmpchain.pem '/-----BEGIN CERTIFICATE-----/' '{*}' +mv $(ls tmpcert-* | tail -1) /etc/config/root.pem +rm tmpcert-* tmpchain.pem +cat /etc/config/root.pem +echo "Fetched valid root certificate from Fulcio to limit entries in CTFE instance" diff --git a/configs/sigstore/rekor/key-gen.sh b/configs/sigstore/rekor/key-gen.sh new file mode 100755 index 0000000..3d604e0 --- /dev/null +++ b/configs/sigstore/rekor/key-gen.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +echo "Generating EC key for P256..." +openssl ecparam -genkey \ + -name prime256v1 \ + -out rekor_key.pem -noout +echo "Completed... (rekor_key.pem)" + +echo "Extracting pubkey..." +openssl ec -in=rekor_key.pem -pubout > rekor_pub_key.pem +echo "Completed... (rekor_pub_key.pem)" \ No newline at end of file diff --git a/configs/tuf/tuf-cli-config.yaml b/configs/tuf/tuf-cli-config.yaml new file mode 100644 index 0000000..f2df7b9 --- /dev/null +++ b/configs/tuf/tuf-cli-config.yaml @@ -0,0 +1,34 @@ +out: "./build" +roles: + root: + keys: + - kind: ecdsa + - kind: ecdsa + - kind: ecdsa + threshold: 2 + version: 1 + timestamp: + keys: + - kind: ecdsa + threshold: 1 + version: 1 + snapshot: + keys: + - kind: ecdsa + threshold: 1 + version: 1 + targets: + keys: + - kind: ecdsa + threshold: 1 + version: 1 +targets: + rekor: + name: "rekor.pub" + filepath: "configs/sigstore/rekor/rekor_pub_key.pem" + fulcio: + name: "fulcio.crt.pem" + filepath: "configs/sigstore/fulcio/config/fulcio.pem" + ctlog: + name: "ctlog.pub" + filepath: "configs/sigstore/fulcio/ctfe-init/config/pubkey.pem" diff --git a/docker-compose.bt4ot.yml b/docker-compose.bt4ot.yml new file mode 100644 index 0000000..a77321b --- /dev/null +++ b/docker-compose.bt4ot.yml @@ -0,0 +1,291 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: '3.8' +services: + bt2x-server: + build: + context: . + dockerfile: dockerfiles/bt2x.Dockerfile + args: + BINARY_NAME: "bt2x-server" + volumes: + - "./configs/bt2x-server:/etc/configs:ro" + - "assisting_auditor:/assisting_auditor" + - "./build:/tuf-repo:ro" + command: [ + "bt2x-server", + "--http", + "--config=/etc/configs/config.yaml", + "--outdir=/assisting_auditor", + "--log-level=debug", + "--interval=1m" + ] + bt2x-ota: + build: + context: . + dockerfile: dockerfiles/bt2x.Dockerfile + args: + BINARY_NAME: "bt2x-ota" + volumes: + - "./configs/bt2x-ota:/etc/configs/config.yaml:ro" + - "assisting_auditor:/assisting_auditor:ro" + - "./build:/tuf-repo:ro" + ports: + - "50000:50000" + command: [ + "bt2x-ota", + "--repo-path=/tuf-repo", + "--signatures=/assisting_auditor", + "--binaries=/assisting_auditor", + ] + bt2x-monitor-1: + build: + context: . + dockerfile: dockerfiles/bt2x.Dockerfile + args: + BINARY_NAME: "bt2x-monitor" + volumes: + - "./configs/bt2x-monitor:/etc/configs:ro" + - "./build/targets/rekor.pub:/etc/monitor/rekor_key.pub" + ports: + - "3132:3132" + command: [ + "bt2x-monitor", + "--sigstore-config=/etc/configs/sigstore-config.yaml", + "--log-interval=1m", + "--gossip-interval=30s", + "--port=3132", + "--monitor=http://bt2x-monitor-2:3131/", + ] + restart: always + + bt2x-monitor-2: + build: + context: . + dockerfile: dockerfiles/bt2x.Dockerfile + args: + BINARY_NAME: "bt2x-monitor" + volumes: + - "./configs/bt2x-monitor/:/etc/configs:ro" + - "./build/targets/rekor.pub:/etc/monitor/rekor_key.pub" + ports: + - "3131:3131" + command: [ + "bt2x-monitor", + "--sigstore-config=/etc/configs/sigstore-config.yaml", + "--log-interval=1m", + "--gossip-interval=30s", + "--port=3131", + "--monitor=http://bt2x-monitor-1:3132/", + ] + restart: always + populate-rekor: + build: + context: . + dockerfile: dockerfiles/populate-rekor.Dockerfile + depends_on: + - rekor + +volumes: + assisting_auditor: { } diff --git a/docker-compose.monitor.yml b/docker-compose.monitor.yml new file mode 100644 index 0000000..5336b44 --- /dev/null +++ b/docker-compose.monitor.yml @@ -0,0 +1,30 @@ +# Copyright 2022 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: '3.8' +services: + rekor-monitor: + build: + context: https://github.com/sigstore/rekor-monitor.git#58b026baada42eb78db8448bf6cc986d4b64e83b + dockerfile: Dockerfile + target: "deploy" + command: [ + "mirroring", + "--file=/etc/rekor-monitor/logInfo.txt" + # uncomment for more frequent polling + # "--interval=1m", + ] + restart: always # keep the server running + volumes: + - ./logInfo.txt:/etc/rekor-monitor/logInfo.txt:z \ No newline at end of file diff --git a/docker-compose.sigstore.yml b/docker-compose.sigstore.yml new file mode 100644 index 0000000..bdcc371 --- /dev/null +++ b/docker-compose.sigstore.yml @@ -0,0 +1,462 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +version: '3.8' +services: + container-registry: + build: + context: . + dockerfile: dockerfiles/oci-registry.Dockerfile + ports: + - "1338:1338" + + dex-idp: + image: dexidp/dex:v2.30.0 + user: root + environment: + - GITHUB_CLIENT_ID + - GITHUB_CLIENT_SECRET + + command: [ + "dex", + "serve", + "/etc/config/dex.yaml", + ] + restart: always # keep the server running + ports: + - "8888:8888" + volumes: + - ./configs/dex:/etc/config/:ro + + + fulcio: + build: + context: https://github.com/sigstore/fulcio.git#main + target: "deploy" + # image: gcr.io/projectsigstore/fulcio@sha256:27c6e4fe64a72a537c133452d9c8e0518944d1d69aeee5e7ef8a9fbe70b8b5d3 + command: [ + "serve", + "--host=0.0.0.0", # The host on which to serve requests for HTTP; --http-host is alias (default "0.0.0.0") + "--port=5555", # The port on which to serve requests for HTTP; --http-port is alias (default "8080") + "--grpc-port=5554", # The port on which to serve requests for GRPC (default "8081") + "--ca=fileca", # googleca | tinkca | pkcs11ca | fileca | kmsca | ephemeralca (for testing) + "--fileca-cert=/etc/fulcio-config/fulcio.pem", # Path to CA certificate + "--fileca-key=/etc/fulcio-config/fulcio_key.pem", # Path to CA encrypted private key + "--fileca-key-passwd=$PRIV_KEY_PASSWORD", # Password to decrypt CA private key + "--config-path=/etc/fulcio-config/config.json", # path to fulcio config json (default "/etc/fulcio-config/config.json") + "--ct-log-public-key-path=/etc/fulcio-config/ct_log_pub_key.pem", # Path to a PEM-encoded public key of the CT log, used to verify SCTs + "--ct-log-url=http://ct-log:6962/sigstore", # host and path (with log prefix at the end) to the ct log (default "http://localhost:6962/test") + "--log_type=dev", # logger type to use (dev/prod) (default "dev") + "--metrics-port=2112", # The port on which to serve prometheus metrics endpoint (default "2112") + ] + ports: + - "5555:5555" + - "5554:5554" + environment: + - PRIV_KEY_PASSWORD + volumes: + - ./configs/sigstore/fulcio/config/config.json:/etc/fulcio-config/config.json:z + - ./configs/sigstore/fulcio/config/fulcio.pem:/etc/fulcio-config/fulcio.pem + - ./configs/sigstore/fulcio/config/fulcio_key.pem:/etc/fulcio-config/fulcio_key.pem + - ./configs/sigstore/fulcio/ctfe-init/config/pubkey.pem:/etc/fulcio-config/ct_log_pub_key.pem:ro + restart: always # keep the server running + healthcheck: + test: [ "CMD", "curl", "-f", "http://fulcio:5555/api/v2/configuration" ] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + depends_on: + - ct-log + read_only: true + + ctfe-init: + build: + context: ./configs/sigstore/fulcio/ctfe-init/ + dockerfile: Dockerfile + depends_on: + - ct-trillian-log-server + volumes: + - ctfe-config:/etc/config/:rw + + ct-log: + image: gcr.io/trillian-opensource-ci/ctfe@sha256:198767025a3e6c591b3d94c06456512150f01b2e88d9b866a9c96bcae29f490f + volumes: + - ctfe-config:/etc/config/:ro + command: [ + "--log_config" ,"/etc/config/ct_server.cfg", + "--log_rpc_server", "ct-trillian-log-server:8090", + "--http_endpoint", "0.0.0.0:6962", + "--alsologtostderr", + ] + restart: always # retry while ctfe-init is running + depends_on: + - ct-trillian-log-server + - ct-trillian-log-signer + - ctfe-init + ports: + - "6962:6962" + + redis-server: + image: docker.io/redis:5.0.10 + command: [ "--bind", "0.0.0.0", "--appendonly", "yes" ] + ports: + - "6379:6379" + hostname: rekor-redis + restart: always # keep the redis server running + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + + mysql: + image: gcr.io/trillian-opensource-ci/db_server@sha256:3037aa063d8f9d1a4e4df90f65b2b37c2b2a2902acf4b47bfb5af0fd150100d9 + environment: + - MYSQL_ROOT_PASSWORD + - MYSQL_DATABASE=sigstore + - MYSQL_USER + - MYSQL_PASSWORD + restart: always # keep the MySQL server running + healthcheck: + test: [ "CMD", "/etc/init.d/mysql", "status" ] + interval: 30s + timeout: 3s + retries: 3 + start_period: 10s + + mysql-bt: + image: gcr.io/trillian-opensource-ci/db_server@sha256:3037aa063d8f9d1a4e4df90f65b2b37c2b2a2902acf4b47bfb5af0fd150100d9 + environment: + - MYSQL_ROOT_PASSWORD + - MYSQL_DATABASE=sigstore + - MYSQL_USER + - MYSQL_PASSWORD + restart: always # keep the MySQL server running + healthcheck: + test: [ "CMD", "/etc/init.d/mysql", "status" ] + interval: 30s + timeout: 3s + retries: 3 + start_period: 10s + + ct-trillian-log-server: + image: gcr.io/projectsigstore/trillian_log_server@sha256:60b1a1cbbb8c58524a60eb748de3be1b4ae03e1c1bf5131a626cec6db785f85b + command: + [ + "--storage_system=mysql", + "--mysql_uri=${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(mysql:3306)/sigstore", + "--mysql_max_idle_conns=2", + "--rpc_endpoint=0.0.0.0:8090", + "--http_endpoint=0.0.0.0:8091", + "--alsologtostderr" + ] + restart: always # retry while mysql is starting up + ports: + - "8090:8090" + - "8091:8091" + depends_on: + - mysql + + ct-trillian-log-signer: + image: gcr.io/projectsigstore/trillian_log_signer@sha256:b2eab2a7ffcb558c827b69fb7c1a7e8bc201dbe9f161b2717c539b8a7d1d8388 + command: + [ + "--storage_system=mysql", + "--mysql_uri=${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(mysql:3306)/sigstore", + "--mysql_max_idle_conns=2", + "--rpc_endpoint=0.0.0.0:8090", + "--http_endpoint=0.0.0.0:8091", + "--force_master", + "--alsologtostderr" + ] + restart: always # retry while mysql is starting up + ports: + - "8092:8091" + depends_on: + - mysql + bt-trillian-log-server: + image: gcr.io/projectsigstore/trillian_log_server@sha256:60b1a1cbbb8c58524a60eb748de3be1b4ae03e1c1bf5131a626cec6db785f85b + command: + [ + "--storage_system=mysql", + "--mysql_uri=${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(mysql-bt:3306)/sigstore", + "--mysql_max_idle_conns=2", + "--rpc_endpoint=0.0.0.0:8090", + "--http_endpoint=0.0.0.0:8091", + "--alsologtostderr" + ] + restart: always # retry while mysql is starting up + # ports: + # - "8090:8090" + # - "8091:8091" + depends_on: + - mysql-bt + + bt-trillian-log-signer: + image: gcr.io/projectsigstore/trillian_log_signer@sha256:b2eab2a7ffcb558c827b69fb7c1a7e8bc201dbe9f161b2717c539b8a7d1d8388 + command: + [ + "--storage_system=mysql", + "--mysql_uri=${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(mysql-bt:3306)/sigstore", + "--mysql_max_idle_conns=2", + "--rpc_endpoint=0.0.0.0:8090", + "--http_endpoint=0.0.0.0:8091", + "--force_master", + "--alsologtostderr" + ] + restart: always # retry while mysql is starting up + # ports: + # - "8093:8091" + depends_on: + - mysql-bt + + rekor: + image: gcr.io/projectsigstore/rekor-server@sha256:f7e6975041b9b6f3afdc7d6a1a87de43098ce8d83eb1958ea097ebfcb5537658 + command: + [ + "serve", + "--trillian_log_server.address=bt-trillian-log-server", + "--trillian_log_server.port=8090", + "--redis_server.address=redis-server", + "--redis_server.port=6379", + "--rekor_server.address=0.0.0.0", + # "--rekor_server.signer=memory", + "--rekor_server.signer=/rekor/rekor_key.pem", + "--enable_attestation_storage", + "--attestation_storage_bucket=file:///var/run/attestations" + ] + volumes: + - "rekor-attestations:/var/run/attestations:z" + - "./configs/sigstore/rekor/rekor_key.pem:/rekor/rekor_key.pem" + restart: always # keep the server running + ports: + - "3000:3000" + depends_on: + - mysql-bt + - redis-server + - bt-trillian-log-server + + rekor-monitor: + build: + context: https://github.com/sigstore/rekor-monitor.git#c24efaf5fab6c82a62b5f5a83a622a10c6aa8466 + dockerfile: Dockerfile + target: "deploy" + command: [ + "mirroring", + #"--file=/etc/rekor-monitor/logInfo.txt" + "--url=http://rekor:3000", + "--interval=5m", + ] + depends_on: + - rekor + restart: always # keep the server running + # volumes: + # - ./logInfo.txt:/etc/rekor-monitor/logInfo.txt:z + + +volumes: + ctfe-config: { } + rekor-attestations: { } diff --git a/dockerfiles/bt2x-monitor.Dockerfile b/dockerfiles/bt2x-monitor.Dockerfile new file mode 100644 index 0000000..2b39b4f --- /dev/null +++ b/dockerfiles/bt2x-monitor.Dockerfile @@ -0,0 +1,213 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM rust:1.67 AS builder +WORKDIR /usr/src/bt2x +RUN apt-get update && apt-get install --yes libudev-dev +COPY . .. +RUN cargo build --release +RUN cargo install --path bt2x-monitor --locked + +FROM debian:bullseye-slim +COPY --from=builder /usr/local/cargo/bin/bt2x-monitor /usr/local/bin/bt2x-monitor +CMD ["bt2x-monitor"] \ No newline at end of file diff --git a/dockerfiles/bt2x-ota.Dockerfile b/dockerfiles/bt2x-ota.Dockerfile new file mode 100644 index 0000000..fec474d --- /dev/null +++ b/dockerfiles/bt2x-ota.Dockerfile @@ -0,0 +1,213 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM rust:1.67 AS builder +WORKDIR /usr/src/bt2x +RUN apt-get update && apt-get install --yes libudev-dev +COPY . .. +RUN cargo build --release +RUN cargo install --path bt2x-ota --locked + +FROM debian:bullseye-slim +COPY --from=builder /usr/local/cargo/bin/bt2x-ota /usr/local/bin/bt2x-ota +CMD ["bt2x-ota"] \ No newline at end of file diff --git a/dockerfiles/bt2x.Dockerfile b/dockerfiles/bt2x.Dockerfile new file mode 100644 index 0000000..063c3d7 --- /dev/null +++ b/dockerfiles/bt2x.Dockerfile @@ -0,0 +1,213 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM rust:1.67 AS builder +WORKDIR /usr/src/bt2x +RUN apt-get update && apt-get install --yes libudev-dev +COPY . .. +RUN cargo build --release + +FROM debian:bullseye-slim +ARG BINARY_NAME +COPY --from=builder /usr/src/bt2x/target/release/${BINARY_NAME} /usr/local/bin/${BINARY_NAME} +CMD ["${BINARY_NAME}"] \ No newline at end of file diff --git a/dockerfiles/oci-registry.Dockerfile b/dockerfiles/oci-registry.Dockerfile new file mode 100644 index 0000000..6ca3b3d --- /dev/null +++ b/dockerfiles/oci-registry.Dockerfile @@ -0,0 +1,206 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang:alpine3.17 +RUN go install github.com/google/go-containerregistry/cmd/registry@v0.13.0 +CMD [ "/go/bin/registry" ] \ No newline at end of file diff --git a/dockerfiles/populate-rekor.Dockerfile b/dockerfiles/populate-rekor.Dockerfile new file mode 100644 index 0000000..d58137f --- /dev/null +++ b/dockerfiles/populate-rekor.Dockerfile @@ -0,0 +1,209 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM bitnami/cosign +USER root +RUN apt-get update && apt-get install --yes curl jq +USER 1001 +COPY populate-rekor.sh . +ENTRYPOINT ["./populate-rekor.sh", "http://rekor:3000"] \ No newline at end of file diff --git a/generate-benchmark-data.py b/generate-benchmark-data.py new file mode 100644 index 0000000..8b6c66e --- /dev/null +++ b/generate-benchmark-data.py @@ -0,0 +1,255 @@ +""" + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" +import subprocess +import os +import json + + +def run_size_benchmarks( + src_path: str, + bin_names: list[str], + outpath: str, +): + print("========== Running size benchmarks ==========") + os.chdir(src_path) + bins = " ".join(f"--bin={b}" for b in bin_names) + print("============ Building artifacts =============") + subprocess.run(f"cargo build --release {bins}".split(" ")) + results = {} + for b in bin_names: + results[b] = {} + subprocess.run(f"cargo objcopy --release --bin={b} -- -O binary target/thumbv6m-none-eabi/release/{b}.bin".split(" ")) + results[b]["raw_size"] = os.stat(f"target/thumbv6m-none-eabi/release/{b}.bin").st_size + bloat_output = subprocess.run( + f"cargo bloat --release --message-format=json -n=0 --bin={b}".split(" "), + capture_output=True, + ).stdout + results[b]["cargo_bloat"] = json.loads(bloat_output) + bloat_output = subprocess.run( + f"cargo bloat --release --message-format=json -n=0 --bin={b} --crates".split(" "), + capture_output=True, + ).stdout + results[b]["cargo_bloat_crates"] = json.loads(bloat_output) + os.chdir("..") + with open(f"{outpath}/benchmarks-size.json", "w+") as f: + json.dump(results, f) + +def run_benchmarks( + outpath: str ="benchmark-output", +): + os.makedirs(outpath, exist_ok=True) + run_size_benchmarks( + src_path="bt2x-pi-pico-w", + bin_names=[ + "benchmark-code-size-full", + "benchmark-code-size-no-tuf", + "benchmark-code-size-signature-only", + "benchmark-code-size-update-only", + ], + outpath="benchmark-output", + ) + + +if __name__ == "__main__": + run_benchmarks() \ No newline at end of file diff --git a/images/development-setup.svg b/images/development-setup.svg new file mode 100644 index 0000000..039d655 --- /dev/null +++ b/images/development-setup.svg @@ -0,0 +1,3 @@ + + +
Rekor
enter
Certificates
to CT Log
Fulcio
CT Log
authenticate Certificate requests
dex-idp
fetch
Binary + Signature Bundles
gossip
BT²X Audit Server
BT²X OTA Server
BT²X Monitor
ID Provider
(e.g. GitHub)
WiFi
Pi Pico W
WiFi
USB
Development
Computer
UART
Pi with
Picoprobe
Firmware
publish Binary + Signature Bundle
request
Signing Certificate
enter Signature
to BT Log
login
BT²X CLI
Docker
Network
container-registry
Target Binary
+
Signature Bundle
Initial Binary
cargo-flash
gossip
\ No newline at end of file diff --git a/images/docker-setup.svg b/images/docker-setup.svg new file mode 100644 index 0000000..3b6b94b --- /dev/null +++ b/images/docker-setup.svg @@ -0,0 +1,3 @@ + + +
rekor
Fulcio
CT Log
dex-idp
BT²X Audit Server
BT²X OTA Server
BT²X Monitor
ID Provider
(e.g. GitHub)
ctfe-init
ct-trillian-log-server
ct-trillian-log-signer
bt-trillian-log-server
bt-trillian-log-signer
redis-server
ct-mysql
bt-mysql
Rekor
CT Log
Sigstore
BT²X
OIDC 
\ No newline at end of file diff --git a/images/general-operation.svg b/images/general-operation.svg new file mode 100644 index 0000000..80573b8 --- /dev/null +++ b/images/general-operation.svg @@ -0,0 +1,3 @@ + + +
timestamps
snapshot
targets
tuf-no-sdt-cli
distribute TUF root
distribute TUF root
root
Rekor
Fulcio
CTLog
run TUF CLI
generate
Sigstore Keys
setup
shell script
issue signing certificate
OIDC flow
Fulcio
CT
Log
Rekor
upload
binary + signatures
upload log entry
BT²X
CLI
authenticate for Fulcio (ODIC)
dex-id
download
binary and signatures
OCI
registry
request inclusion proofs
gossip
BT²X
Auditing Server
BT²X
OTA Server
TUF
Repo
download
TUF files + binary + signature
Pi Pico W
verify and update
request
consistency proofs
Monitor
generate keys
and metafiles
distribute TUF repo
initialization
operation
compile
binary
\ No newline at end of file diff --git a/images/src/development-setup.drawio b/images/src/development-setup.drawio new file mode 100644 index 0000000..35e2cae --- /dev/null +++ b/images/src/development-setup.drawio @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/src/docker-setup.drawio b/images/src/docker-setup.drawio new file mode 100644 index 0000000..b02496a --- /dev/null +++ b/images/src/docker-setup.drawio @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/src/general-operation.drawio b/images/src/general-operation.drawio new file mode 100644 index 0000000..92e591b --- /dev/null +++ b/images/src/general-operation.drawio @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/performance_benchmarks.py b/performance_benchmarks.py new file mode 100644 index 0000000..4b4e372 --- /dev/null +++ b/performance_benchmarks.py @@ -0,0 +1,318 @@ +""" + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" +import matplotlib.pyplot as plt +import numpy as np +import datetime +from benchmark_server import BenchmarkData +from matplotlib.patches import Polygon +import os +from datetime import timedelta +from itertools import product +from functools import reduce + +if __name__ == "__main__": + plt.style.use("Solarize_Light2") + files = [f for f in os.listdir("../benchmark-output") if f.startswith("performance-benchmark")] + data = [BenchmarkData.parse_file(f"../benchmark-output/{f}") for f in files] + data.sort(key=lambda x: x.binary_size) + data_fetch_targets = [m.fetch_target_files for m in data] + data_update_repo = [m.update_repo for m in data] + data_fetch_signature = [m.fetch_signature for m in data] + data_fetch_binary = [m.fetch_binary for m in data] + print(data_fetch_binary) + def helper(acc: dict, a): + if a.binary_size not in acc: + acc[a.binary_size] = [] + acc[a.binary_size].append(a) + return acc + data_by_size = reduce(helper, data, {}) + data_fetch_binary_by_size = {k:[d.fetch_binary for d in vals] for (k,vals) in data_by_size.items()} + data_fetch_binary_by_size = {k: [timedelta(seconds=d["secs"] + d["nanos"]*1e-9).total_seconds() for d in vals] for (k,vals) in data_fetch_binary_by_size.items()} + print(data_fetch_binary_by_size) + data_verify_binary = [m.verify_binary for m in data] + data_fetch_targets = [timedelta(seconds=d["secs"] + d["nanos"]*1e-9).total_seconds() for d in data_fetch_targets] + data_update_repo = [timedelta(seconds=d["secs"] + d["nanos"]*1e-9).total_seconds() for d in data_update_repo] + data_fetch_signature = [timedelta(seconds=d["secs"] + d["nanos"]*1e-9).total_seconds() for d in data_fetch_signature] + data_fetch_binary = [timedelta(seconds=d["secs"] + d["nanos"]*1e-9).total_seconds() for d in data_fetch_binary] + data_verify_binary_full = [timedelta(seconds=d["secs"] + d["nanos"]*1e-9).total_seconds() for d in data_verify_binary] + data_verify_binary = {k:[d.verify_binary for d in vals] for (k,vals) in data_by_size.items()} + data_verify_binary = {k: [timedelta(seconds=d["secs"] + d["nanos"]*1e-9).total_seconds() for d in vals] for (k,vals) in data_verify_binary.items()} + + # of each data type for the addition + data_full = {k:[ + (timedelta(seconds=d.fetch_target_files["secs"] + d.fetch_target_files["nanos"] * 1e-9) + + timedelta(seconds=d.update_repo["secs"] + d.update_repo["nanos"] * 1e-9) + + timedelta(seconds=d.fetch_signature["secs"] + d.fetch_signature["nanos"] * 1e-9) + + timedelta(seconds=d.fetch_binary["secs"] + d.fetch_binary["nanos"] * 1e-9) + + timedelta(seconds=d.verify_binary["secs"] + d.verify_binary["nanos"] * 1e-9) + ).total_seconds() + for d in vals + ] for (k,vals) in data_by_size.items()} + data_no_tuf = {k:[ + (timedelta(seconds=d.fetch_signature["secs"] + d.fetch_signature["nanos"] * 1e-9) + + timedelta(seconds=d.fetch_binary["secs"] + d.fetch_binary["nanos"] * 1e-9) + + timedelta(seconds=d.verify_binary["secs"] + d.verify_binary["nanos"] * 1e-9) + ).total_seconds() + for d in vals + ] for (k,vals) in data_by_size.items()} + # data_full_synthetic = [sum(t for t in d) for d in product(data_fetch_targets,data_update_repo,data_fetch_signature,data_fetch_binary,data_verify_binary)] + # data_no_tuf_synthetic = [sum(t for t in d) for d in product(data_fetch_signature, data_fetch_binary, data_verify_binary)] + # basic plot + fig, axs = plt.subplots(3, 2) + + + axs[0, 0].boxplot(data_fetch_targets) + axs[0, 0].set_title("Fetch TUF Targets") + axs[0, 0].set_ylabel("time (s)") + axs[1, 0].set_ylabel("time (s)") + axs[0, 1].boxplot(data_update_repo) + axs[0, 1].set_title("TUF Repo Update") + axs[1, 0].boxplot(data_fetch_signature) + axs[1, 0].set_title("Signature Fetching") + axs[1, 1].set_title("Verification Duration by size") + axs[2, 0].set_title("Binary fetch duration by size") + axs[1, 1].boxplot( + [data_verify_binary_full] + list(data_verify_binary.values()), + tick_labels=["all samples"] + [f"{k/1024:.1f} KiB" for k in data_verify_binary.keys()] + ) + + + axs[2, 0].plot( + [0, 1] + [f"{k/1024:.1f} KiB" for k in data_fetch_binary_by_size.keys()], + [None, None] + [np.average(v) for v in data_fetch_binary_by_size.values()], + linestyle="--" + ) + axs[2, 0].boxplot( + [data_fetch_binary] + list(data_fetch_binary_by_size.values()), + tick_labels=["all samples"] + [f"{k/1024:.1f} KiB" for k in data_fetch_binary_by_size.keys()] + ) + axs[2, 1].plot( + [0] + [f"{k/1024:.1f} KiB" for k in data_full.keys()], + [None] + [np.average(v) for v in data_full.values()], + label="TUF + BT", + linestyle="dotted", + ) + axs[2, 1].plot( + [0] + [f"{k/1024:.1f} KiB" for k in data_no_tuf.keys()], + [None] + [np.average(v) for v in data_no_tuf.values()], + label="BT only", + linestyle="dashed", + ) + axs[2, 1].boxplot( + data_full.values(), + tick_labels=[f"{k/1024:.1f} KiB" for k in data_full.keys()], + ) + axs[2, 0].set_ylabel("time (s)") + + axs[2, 1].boxplot( + data_no_tuf.values(), + tick_labels=[f"{k/1024:.1f} KiB" for k in data_no_tuf.keys()] + ) + + axs[2, 1].legend() + axs[2, 1].set_title("Cumulative Duration by size") + + fig.subplots_adjust(left=0.08, right=0.98, bottom=0.05, top=0.9, + hspace=0.4, wspace=0.3) + plt.show() \ No newline at end of file diff --git a/pico-w-bootloader/.cargo/config.toml b/pico-w-bootloader/.cargo/config.toml new file mode 100644 index 0000000..9d48ecd --- /dev/null +++ b/pico-w-bootloader/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/pico-w-bootloader/.dockerignore b/pico-w-bootloader/.dockerignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/pico-w-bootloader/.dockerignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/pico-w-bootloader/.gitignore b/pico-w-bootloader/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/pico-w-bootloader/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/pico-w-bootloader/Cargo.toml b/pico-w-bootloader/Cargo.toml new file mode 100644 index 0000000..60a5969 --- /dev/null +++ b/pico-w-bootloader/Cargo.toml @@ -0,0 +1,33 @@ +[package] +edition = "2021" +name = "rp-bootloader-example" +version = "0.1.0" +description = "Example bootloader for RP2040 chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-rp = { version = "0.1", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [] } +embassy-boot-rp = { version = "0.2", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +embassy-sync = { version = "0.6", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180" } +embassy-time = { version = "0.3", git = "https://github.com/embassy-rs/embassy", rev = "a2acb3e3dceddb4752f8fb1c17aa65e1959a2180", features = [] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-boot-rp/defmt", + "embassy-rp/defmt", +] + +[profile.release] +debug = true +opt-level = 's' diff --git a/pico-w-bootloader/LICENSE-APACHE b/pico-w-bootloader/LICENSE-APACHE new file mode 100644 index 0000000..8f7956e --- /dev/null +++ b/pico-w-bootloader/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) Embassy project contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/pico-w-bootloader/LICENSE-CC-BY-SA b/pico-w-bootloader/LICENSE-CC-BY-SA new file mode 100644 index 0000000..a73481c --- /dev/null +++ b/pico-w-bootloader/LICENSE-CC-BY-SA @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/pico-w-bootloader/LICENSE-MIT b/pico-w-bootloader/LICENSE-MIT new file mode 100644 index 0000000..1fe5730 --- /dev/null +++ b/pico-w-bootloader/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) Embassy project contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/pico-w-bootloader/README.md b/pico-w-bootloader/README.md new file mode 100644 index 0000000..406dc2f --- /dev/null +++ b/pico-w-bootloader/README.md @@ -0,0 +1,4 @@ +# Bootloader for RP2040 + +- [Refer to the Embassy repository](https://github.com/embassy-rs/embassy/tree/main/examples/boot/bootloader/rp) from which this code was copied. +- The original licenses are included in this folder. diff --git a/pico-w-bootloader/build.rs b/pico-w-bootloader/build.rs new file mode 100644 index 0000000..c201704 --- /dev/null +++ b/pico-w-bootloader/build.rs @@ -0,0 +1,28 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/pico-w-bootloader/main.rs b/pico-w-bootloader/main.rs new file mode 100644 index 0000000..a329728 --- /dev/null +++ b/pico-w-bootloader/main.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_rp::*; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Duration; + +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + + // Uncomment this if you are debugging the bootloader with debugger/RTT attached, + // as it prevents a hard fault when accessing flash 'too early' after boot. + + for i in 0..10000000 { + cortex_m::asm::nop(); + } + + + let flash = WatchdogFlash::::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); + let active_offset = config.active.offset(); + let bl: BootLoader = BootLoader::prepare(config); + + unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) } +} + +#[no_mangle] +#[cfg_attr(target_os = "none", link_section = ".HardFault.user")] +unsafe extern "C" fn HardFault() { + cortex_m::peripheral::SCB::sys_reset(); +} + +#[exception] +unsafe fn DefaultHandler(_: i16) -> ! { + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16; + + panic!("DefaultHandler #{:?}", irqn); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + cortex_m::asm::udf(); +} diff --git a/pico-w-bootloader/memory.x b/pico-w-bootloader/memory.x new file mode 100644 index 0000000..c3b5497 --- /dev/null +++ b/pico-w-bootloader/memory.x @@ -0,0 +1,31 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x10007000, LENGTH = 512K + DFU : ORIGIN = 0x10087000, LENGTH = 516K + + /* Pick one of the two options for RAM layout */ + + /* OPTION A: Use all RAM banks as one big block */ + /* Reasonable, unless you are doing something */ + /* really particular with DMA or other concurrent */ + /* access that would benefit from striping */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K + + /* OPTION B: Keep the unstriped sections separate */ + /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ + /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ + /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOT2); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOT2); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(BOOT2); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(BOOT2); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOT2); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOT2); diff --git a/pico-w-bootloader/src/main.rs b/pico-w-bootloader/src/main.rs new file mode 100644 index 0000000..a329728 --- /dev/null +++ b/pico-w-bootloader/src/main.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_rp::*; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Duration; + +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + + // Uncomment this if you are debugging the bootloader with debugger/RTT attached, + // as it prevents a hard fault when accessing flash 'too early' after boot. + + for i in 0..10000000 { + cortex_m::asm::nop(); + } + + + let flash = WatchdogFlash::::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); + let active_offset = config.active.offset(); + let bl: BootLoader = BootLoader::prepare(config); + + unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) } +} + +#[no_mangle] +#[cfg_attr(target_os = "none", link_section = ".HardFault.user")] +unsafe extern "C" fn HardFault() { + cortex_m::peripheral::SCB::sys_reset(); +} + +#[exception] +unsafe fn DefaultHandler(_: i16) -> ! { + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16; + + panic!("DefaultHandler #{:?}", irqn); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + cortex_m::asm::udf(); +} diff --git a/populate-rekor.sh b/populate-rekor.sh new file mode 100755 index 0000000..6e2918f --- /dev/null +++ b/populate-rekor.sh @@ -0,0 +1,219 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#!/usr/bin/env sh +REKOR_URL=$1 +echo "Using rekor url: $REKOR_URL" +tree_size="$(curl "$REKOR_URL"/api/v1/log/ | jq '.treeSize')" +echo "Current tree size: $tree_size" +if [ "$tree_size" -eq "0" ]; then + echo "Tree is empty, populating ..." + echo "Hello Rekor!" > hello-rekor.txt + # create key pair, sign and publish to rekor + printf 'sigstore\nsigstore\n' | cosign generate-key-pair | exit 1 + printf 'sigstore\nsigstore\n' | COSIGN_EXPERIMENTAL=1 cosign sign-blob --rekor-url="$REKOR_URL" --key=cosign.key --yes hello-rekor.txt | exit 1 + echo "Tree is now populated!" +else + echo "Tree is already populated!" +fi + diff --git a/process-benchmark-data.py b/process-benchmark-data.py new file mode 100644 index 0000000..b5a38e4 --- /dev/null +++ b/process-benchmark-data.py @@ -0,0 +1,86 @@ +import matplotlib.pyplot as plt +import json + +crypto_libs = [ + "p256", + "sha2", + "pem_rfc7468", + "spki", + "pkcs8", + "der", + "der?", + "x509_cert", + "serde_json", + "const_oid", + "ecdsa", + "base64ct", + "sec1", + "crypto_bigint", + "heapless", + "smoltcp", + "embassy_net", + "fixed", + "hash32", +] + +def process_size_benchmarks(data_path:str): + plt.style.use("Solarize_Light2") + print("processing size benchmarks") + with open(data_path) as f: + raw_data = json.load(f) + + fig, ax = plt.subplots() + + file_size_data = [(name, data["raw_size"]/(1024**1)) for (name,data) in raw_data.items()] + file_text_section = [(name, data["cargo_bloat_crates"]["text-section-size"]/(1024**1)) for (name,data) in raw_data.items()] + tuf_no_std_size = [] + bt2x_size = [] + libs_size = [] + for (name, data) in raw_data.items(): + crates = data["cargo_bloat_crates"]["crates"] + size_tuf_no_std = sum(c["size"] for c in crates if c["name"].startswith("tuf_no_std")) / (1024**1) + size_bt2x = sum(c["size"] for c in crates if c["name"].startswith("bt2x")) / (1024**1) + size_libs = sum(c["size"] for c in crates if any(c["name"].startswith(l) for l in crypto_libs)) / (1024**1) + libs_size.append((name, size_libs, size_libs)) + bt2x_size.append((name, size_bt2x + size_libs, size_bt2x)) + tuf_no_std_size.append((name, size_tuf_no_std + size_bt2x + size_libs, size_tuf_no_std)) + + tuf_no_std_size = list(zip(*tuf_no_std_size)) + bt2x_size = list(zip(*bt2x_size)) + libs_size = list(zip(*libs_size)) + + ax.set_ylabel("size in KiB (1024B)") + ax.bar(*zip(*file_size_data),align="edge", label="Entire Binary", width=0.8) + ax.bar(*zip(*file_text_section),align="edge", label=".text section", width=0.75, hatch="o") + ax.bar(tuf_no_std_size[0], tuf_no_std_size[1], align="edge", label="TUF", width=0.7, hatch="+") + ax.bar(bt2x_size[0], bt2x_size[1],align="edge", label="BT²X", width=0.7, hatch="//") + ax.bar(libs_size[0], libs_size[1],align="edge", label="Libs", width=0.7, hatch="\\\\") + ax.legend(loc="upper right") + + table_data = { + "Entire Binary": [f"{v:.2f} KiB" for (_, v) in file_size_data], + ".text section": [f"{v:.2f} KiB" for (_, v) in file_text_section], + "TUF": [f"{v:.2f} KiB" for v in tuf_no_std_size[2]], + "BT²X": [f"{v:.2f} KiB" for v in bt2x_size[2]], + "Libs": [f"{v:.2f} KiB" for v in libs_size[2]], + } + + table = plt.table( + colWidths=[0.25, 0.25, 0.25, 0.25], + cellText=list(table_data.values()), + rowLabels=list(table_data.keys()), + colLabels=list(raw_data.keys()), + ) + table.auto_set_font_size(False) + table.set_fontsize(8) + plt.subplots_adjust(left=0.2, bottom=0.2) + plt.xticks([]) + plt.show() + + +def process_benchmarks(): + process_size_benchmarks("benchmark-output/benchmarks-size.json") + +if __name__ == "__main__": + print("processing benchmarks") + process_benchmarks() \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..78eb172 --- /dev/null +++ b/readme.md @@ -0,0 +1,145 @@ +# BT²X + +Multi-Leveled Binary Transparency to Protect the Software Supply Chain of Operational Technology. + +**The code in this repository is experimental and not to be used in production.** + +**This project is not maintained. It has been published as part of the scientific paper specified in the next section.** + +## BibTeX Citation + +If you use BT²X in a scientific publication, we would appreciate using the following citation: + +``` +@inproceedings{10.1145/3690134.3694816, +author = {Heinl, Michael P. and Embacher, Victor}, +title = {BT²X: Multi-Leveled Binary Transparency to Protect the Software Supply Chain of Operational Technology}, +year = {2024}, +publisher = {Association for Computing Machinery}, +address = {New York, NY, USA}, +url = {https://doi.org/10.1145/3690134.3694816}, +doi = {10.1145/3690134.3694816}, +booktitle = {Proceedings of the Sixth Workshop on CPS&IoT Security and Privacy (CPSIoTSec’24)}, +keywords = {digital certificates, X.509, ASN.1, parsing, TLS libraries, conformity testing}, +location = {Salt Lake City, UT, USA}, +series = {CPSIoTSec’24} +} + +``` + +## License + +This project is distributed under the Apache-2.0 License, see [LICENSE.txt](LICENSE.txt) for details. + +## Project Structure + +This repository contains a lot of code, however, it is separated into modules at a best-effort basis to improve understandability. **This readme aims to provide a high-level entry point to the project, the subdirectories and libraries have their own documentation as well.** + +We have the following major components: +- The `bt2x` directory contains most of the modules related to the binary transparency functionality. +- The `tuf-no-std` directory contains a `no-std` Rust implementation of [TUF](https://theupdateframework.io/) based on the [DER format](https://en.wikipedia.org/wiki/X.690#DER_encoding). +- The `bt2x-pi-pico-w` directory contains the code for a dummy application with an OTA updater as well as the binaries that were used for benchmarking. +- The `bt2x-embassy-std` directory contains code that can be used to run the embedded code (which mostly depends on [embassy](https://embassy.dev/) and other embedded libraries) on a regular host. +- The `benchmarking` directory contains the code to analyze the benchmark data as well as the code to gather/process the data. +- The `configs` directory contains configuration files that are used for the docker setup and also serve as an example on how to configure the applications. + +## Documentation + +This README is not the only place of documentation. +Some of the subdirectories have their own READMEs. +Additionally, there is additional documentation added to the Rust code via `rustdoc`, +which you can build and browse by running the following shell script. + +```sh +./build-docs.sh +``` + +## General Operation + +The following figure shows how the architecture roughly works. + +![General Operation](images/general-operation.svg) + +## Docker Setup + +The docker setup includes the Sigstore components as well as the BT²X components. +There is some redundancy in the components, as both Rekor and the CT log are implemented as Trillian *personalities*. Trillian itself is run as two containers and an SQL server. + +Additionally, in our example, the OIDC provider `dex-idp` is reliant on an external ID provider (e.g., the GitHub OAuth 2.0 provider). + +The following figure shows which containers belong to what system. + +![Docker Setup](images/docker-setup.svg) + + +## Getting Started + +### Development Setup + +This following figure shows how different hardware components interact with each other/how they are "wired", and how binaries flow through the system. + +![Development Setup](images/development-setup.svg) + +### Dependencies + +- Install the Rust toolchain via [rustup](https://rustup.rs/). +- Make sure you have OpenSSL installed. +- [This guide](https://reltech.substack.com/p/getting-started-with-rust-on-a-raspberry) is helpful to set up the dependencies for the Pico W development setup. + +### Initial Setup + +1. Run the `setup.sh` script to generate the keys and TUF repo. This might take a while in cases where everything needs to be compiled. +2. [Create a GitHub OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) with the URL `http://dex-idp/` and the authorization callback URL `http://dex-idp:8888/callback` +3. Create a `.env` file in the root directory with the following variables: +```env +MYSQL_ROOT_PASSWORD=zaphod +MYSQL_USER=test +MYSQL_PASSWORD=zaphod +KEYCLOAK_ADMIN_PASSWORD=admin +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +PRIV_KEY_PASSWORD=sigstore +``` +4. You need to modify `configs/bt2x-server/config.yaml` and add the correct e-Mail address + +### Starting the Infrastructure + +Start the infrastructure with the following command. **Note:** this starts a relatively large number of docker contains and requires some resources. Also building the containers can take a while. + +```sh +docker compose -f=docker-compose.sigstore.yml -f=docker-compose.bt2x.yml up -d +``` + +You can remove the containers with the following command: + +```sh +docker compose -f=docker-compose.sigstore.yml -f=docker-compose.bt2x.yml down +``` + +### Uploading a Signed Binary to the Infrastructure + +1. In the `bt2x` directory, run the command to build the CLI binary: +```sh +cargo build --release --bin=bt2x-cli +``` +2. In the `bt2x-pi-pico-w` directory, run the following command: + ```sh +../bt2x/target/release/bt2x-cli --tag=localhost:1338/bt2x/pi-pico:bin --http --rekor-url=http://localhost:3000 --rekor-public-key=../sigstore/rekor/rekor_key.pem --fulcio-url=http://localhost:5555 --fulcio-public-key=../sigstore/configs/fulcio/config/fulcio.pem --bin=blinky --target=thumbv6m-none-eabi --log-level=debug --ct-log-public-key=../configs/sigstore/fulcio/ctfe-init/config/pubkey.pem --oidc-issuer=http://dex-idp:8888/ +``` + +### Building/Flashing the Pico W Binaries + + +## Troubleshooting + +### Something is complaining about Rekor have a log index of 0 + +Cause: this can happen if the log is empty or you are trying to run operations on an item with the log index of 0. + +Fix it by running: +- `docker compose -f=docker-compose.sigstore.yml -f=docker-compose.bt4ot.yml up populate-rekor -d` +- or by running the publishing CLI tool again. + +## "Error entering certificate in CTL" + +You might get this error when using the CLI tool. You can fix it by removing the `ctfe-config` docker volume and restarting the setup. diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..ffcd0ab --- /dev/null +++ b/setup.sh @@ -0,0 +1,222 @@ +#"" +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +# +# TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# +# 1. Definitions. +# +# "License" shall mean the terms and conditions for use, reproduction, +# and distribution as defined by Sections 1 through 9 of this document. +# +# "Licensor" shall mean the copyright owner or entity authorized by +# the copyright owner that is granting the License. +# +# "Legal Entity" shall mean the union of the acting entity and all +# other entities that control, are controlled by, or are under common +# control with that entity. For the purposes of this definition, +# "control" means (i) the power, direct or indirect, to cause the +# direction or management of such entity, whether by contract or +# otherwise, or (ii) ownership of fifty percent (50%) or more of the +# outstanding shares, or (iii) beneficial ownership of such entity. +# +# "You" (or "Your") shall mean an individual or Legal Entity +# exercising permissions granted by this License. +# +# "Source" form shall mean the preferred form for making modifications, +# including but not limited to software source code, documentation +# source, and configuration files. +# +# "Object" form shall mean any form resulting from mechanical +# transformation or translation of a Source form, including but +# not limited to compiled object code, generated documentation, +# and conversions to other media types. +# +# "Work" shall mean the work of authorship, whether in Source or +# Object form, made available under the License, as indicated by a +# copyright notice that is included in or attached to the work +# (an example is provided in the Appendix below). +# +# "Derivative Works" shall mean any work, whether in Source or Object +# form, that is based on (or derived from) the Work and for which the +# editorial revisions, annotations, elaborations, or other modifications +# represent, as a whole, an original work of authorship. For the purposes +# of this License, Derivative Works shall not include works that remain +# separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof. +# +# "Contribution" shall mean any work of authorship, including +# the original version of the Work and any modifications or additions +# to that Work or Derivative Works thereof, that is intentionally +# submitted to Licensor for inclusion in the Work by the copyright owner +# or by an individual or Legal Entity authorized to submit on behalf of +# the copyright owner. For the purposes of this definition, "submitted" +# means any form of electronic, verbal, or written communication sent +# to the Licensor or its representatives, including but not limited to +# communication on electronic mailing lists, source code control systems, +# and issue tracking systems that are managed by, or on behalf of, the +# Licensor for the purpose of discussing and improving the Work, but +# excluding communication that is conspicuously marked or otherwise +# designated in writing by the copyright owner as "Not a Contribution." +# +# "Contributor" shall mean Licensor and any individual or Legal Entity +# on behalf of whom a Contribution has been received by Licensor and +# subsequently incorporated within the Work. +# +# 2. Grant of Copyright License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# copyright license to reproduce, prepare Derivative Works of, +# publicly display, publicly perform, sublicense, and distribute the +# Work and such Derivative Works in Source or Object form. +# +# 3. Grant of Patent License. Subject to the terms and conditions of +# this License, each Contributor hereby grants to You a perpetual, +# worldwide, non-exclusive, no-charge, royalty-free, irrevocable +# (except as stated in this section) patent license to make, have made, +# use, offer to sell, sell, import, and otherwise transfer the Work, +# where such license applies only to those patent claims licensable +# by such Contributor that are necessarily infringed by their +# Contribution(s) alone or by combination of their Contribution(s) +# with the Work to which such Contribution(s) was submitted. If You +# institute patent litigation against any entity (including a +# cross-claim or counterclaim in a lawsuit) alleging that the Work +# or a Contribution incorporated within the Work constitutes direct +# or contributory patent infringement, then any patent licenses +# granted to You under this License for that Work shall terminate +# as of the date such litigation is filed. +# +# 4. Redistribution. You may reproduce and distribute copies of the +# Work or Derivative Works thereof in any medium, with or without +# modifications, and in Source or Object form, provided that You +# meet the following conditions: +# +# (a) You must give any other recipients of the Work or +# Derivative Works a copy of this License; and +# +# (b) You must cause any modified files to carry prominent notices +# stating that You changed the files; and +# +# (c) You must retain, in the Source form of any Derivative Works +# that You distribute, all copyright, patent, trademark, and +# attribution notices from the Source form of the Work, +# excluding those notices that do not pertain to any part of +# the Derivative Works; and +# +# (d) If the Work includes a "NOTICE" text file as part of its +# distribution, then any Derivative Works that You distribute must +# include a readable copy of the attribution notices contained +# within such NOTICE file, excluding those notices that do not +# pertain to any part of the Derivative Works, in at least one +# of the following places: within a NOTICE text file distributed +# as part of the Derivative Works; within the Source form or +# documentation, if provided along with the Derivative Works; or, +# within a display generated by the Derivative Works, if and +# wherever such third-party notices normally appear. The contents +# of the NOTICE file are for informational purposes only and +# do not modify the License. You may add Your own attribution +# notices within Derivative Works that You distribute, alongside +# or as an addendum to the NOTICE text from the Work, provided +# that such additional attribution notices cannot be construed +# as modifying the License. +# +# You may add Your own copyright statement to Your modifications and +# may provide additional or different license terms and conditions +# for use, reproduction, or distribution of Your modifications, or +# for any such Derivative Works as a whole, provided Your use, +# reproduction, and distribution of the Work otherwise complies with +# the conditions stated in this License. +# +# 5. Submission of Contributions. Unless You explicitly state otherwise, +# any Contribution intentionally submitted for inclusion in the Work +# by You to the Licensor shall be under the terms and conditions of +# this License, without any additional terms or conditions. +# Notwithstanding the above, nothing herein shall supersede or modify +# the terms of any separate license agreement you may have executed +# with Licensor regarding such Contributions. +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor, +# except as required for reasonable and customary use in describing the +# origin of the Work and reproducing the content of the NOTICE file. +# +# 7. Disclaimer of Warranty. Unless required by applicable law or +# agreed to in writing, Licensor provides the Work (and each +# Contributor provides its Contributions) on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied, including, without limitation, any warranties or conditions +# of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +# PARTICULAR PURPOSE. You are solely responsible for determining the +# appropriateness of using or redistributing the Work and assume any +# risks associated with Your exercise of permissions under this License. +# +# 8. Limitation of Liability. In no event and under no legal theory, +# whether in tort (including negligence), contract, or otherwise, +# unless required by applicable law (such as deliberate and grossly +# negligent acts) or agreed to in writing, shall any Contributor be +# liable to You for damages, including any direct, indirect, special, +# incidental, or consequential damages of any character arising as a +# result of this License or out of the use or inability to use the +# Work (including but not limited to damages for loss of goodwill, +# work stoppage, computer failure or malfunction, or any and all +# other commercial damages or losses), even if such Contributor +# has been advised of the possibility of such damages. +# +# 9. Accepting Warranty or Additional Liability. While redistributing +# the Work or Derivative Works thereof, You may choose to offer, +# and charge a fee for, acceptance of support, warranty, indemnity, +# or other liability obligations and/or rights consistent with this +# License. However, in accepting such obligations, You may act only +# on Your own behalf and on Your sole responsibility, not on behalf +# of any other Contributor, and only if You agree to indemnify, +# defend, and hold each Contributor harmless for any liability +# incurred by, or claims asserted against, such Contributor by reason +# of your accepting any such warranty or additional liability. +# +# END OF TERMS AND CONDITIONS +# +# APPENDIX: How to apply the Apache License to your work. +# +# To apply the Apache License to your work, attach the following +# boilerplate notice, with the fields enclosed by brackets "[]" +# replaced with your own identifying information. (Don't include +# the brackets!) The text should be enclosed in the appropriate +# comment syntax for the file format. We also recommend that a +# file or class name and description of purpose be included on the +# same "printed page" as the copyright notice for easier +# identification within third-party archives. +# +# Copyright 2024 Fraunhofer AISEC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo "creating rekor key ..." +cd configs/sigstore/rekor || exit +./key-gen.sh +echo "complete" +cd - || exit + +echo "creating fulcio cert ..." +cd configs/sigstore/fulcio/config || exit +./cert-gen.sh +echo "complete" +cd - || exit + +echo "compiling tuf-no-std" +cd tuf-no-std || exit +cargo build --release +echo "creating creating TUF keys and metadata in './build' ..." +cd - || exit +tuf-no-std/target/release/tuf-no-std-cli configs/tuf/tuf-cli-config.yaml +echo "complete" \ No newline at end of file diff --git a/tuf-no-std/.dockerignore b/tuf-no-std/.dockerignore new file mode 100644 index 0000000..66a6354 --- /dev/null +++ b/tuf-no-std/.dockerignore @@ -0,0 +1,16 @@ +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/tuf-no-std/.gitignore b/tuf-no-std/.gitignore new file mode 100755 index 0000000..bf17ce4 --- /dev/null +++ b/tuf-no-std/.gitignore @@ -0,0 +1,8 @@ +/target +/Cargo.lock +/out/targets/ctlog.pub +/out/targets/rekor.pub +/out/1.root.der +/out/1.snapshot.der +/out/1.targets.der +/out/1.timestamp.der diff --git a/tuf-no-std/Cargo.toml b/tuf-no-std/Cargo.toml new file mode 100644 index 0000000..4d7f57b --- /dev/null +++ b/tuf-no-std/Cargo.toml @@ -0,0 +1,42 @@ +[workspace] +members = [ + "tuf-no-std-common", + "tuf-no-std-der", + "tuf-no-std", + "tuf-no-std-cli", +] +resolver = "2" + +[workspace.dependencies] +anyhow = { version = "1.0.75", default-features = false } +async-trait = "0.1" +clap = "4.5" +const-oid = { version = "0.9.5", default-features = false } +der = { version = "0.7.8", default-features = false } +ed25519 = { version = "2.2", default-features = false } +ed25519-dalek = { version = "2.0", default-features = false } +either = { version = "1.8", default-features = false } +hash32 = "0.3.1" +heapless = "0.8.0" +hex = { version = "0.4.3", default-features = false } +hex-literal = "0.4" +olpc-cjson = "0.1" +# oqs = { version = "0.9.0", default-features = false } +p256 = { version = "0.13", default-features = false } +pem = "3.0.4" +pem-rfc7468 = { version = "0.7", default-features = false } +postcard = "1.0" +rand = "0.8.5" +rand_core = { version = "0.6.4", default-features = false } +ring = "0.17.8" +rsa = { version = "0.9", default-features = false } +serde = { version = "1.0", default-features = false } +serde-json-core = "0.5" +serde_json = { version = "1.0", default-features = false } +sha2 = { version = "0.10.7", default-features = false } +signature = { version = "2", default-features = false } +spki = { version = "0.7.2", default-features = false } +tokio = "1" +tough = "0.15.0" +untrusted = "0.9.0" +zeroize = { version = "1.6", default-features = false } diff --git a/tuf-no-std/LICENSE.txt b/tuf-no-std/LICENSE.txt new file mode 100644 index 0000000..4052ad4 --- /dev/null +++ b/tuf-no-std/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Fraunhofer AISEC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tuf-no-std/README.md b/tuf-no-std/README.md new file mode 100644 index 0000000..fa3d6f8 --- /dev/null +++ b/tuf-no-std/README.md @@ -0,0 +1,21 @@ +# tuf-no-std + +- proof-of-concept `no-std` Rust implementation for TUF based on the DER format +- attempts to adhere to the TUF specification as far as possible +- possible that some aspects of the code fail to do so + +## Project Structure + +The project is divided into the following modules. +- `tuf-no-std`: most of the logic resides here, some of the code could be split into the other crates. +- `tuf-no-cli`: tool to create a TUF repo, not very flexible; also serves as an example on how to use the builders from the `tuf-no-std` crate. +- `tuf-no-common`: crate common functionalities such as error types, traits, constants, etc. that need to be available to other non-top level modules. +- `tuf-no-der`: TUF data structures for the DER format. + +## Building the Docs + +To view the documentation within the code (RustDocs) run the following command: + +```sh +cargo doc --open +``` diff --git a/tuf-no-std/deny.toml b/tuf-no-std/deny.toml new file mode 100644 index 0000000..46c1523 --- /dev/null +++ b/tuf-no-std/deny.toml @@ -0,0 +1,237 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "Unicode-DFS-2016", + "BSD-3-Clause", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/tuf-no-std/test-scenarios.md b/tuf-no-std/test-scenarios.md new file mode 100644 index 0000000..40296d3 --- /dev/null +++ b/tuf-no-std/test-scenarios.md @@ -0,0 +1,77 @@ +## Test Scenarios for Client Updates + +### Update Root Role + +- Refer to [the corresponding TUF specification section](https://theupdateframework.github.io/specification/v1.0.33/index.html#update-root). +- [ ] *"[T]he client MUST download intermediate root metadata files, until the latest available one is reached"* +- [x] *"Check for arbitrary Software Attack"* + - [x] *"Version N+1 of the root metadata file MUST have been signed by:*" + - [x] *"a THRESHOLD of keys specified in the trusted root metadata file (version N)"* + - [x] *"a THRESHOLD of keys specified in the new root metadata file being validated (version N+1)."* + - [x] *"When computing the THRESHOLD each KEY MUST only contribute one SIGNATURE."* + - [x] *"each SIGNATURE which is counted towards the THRESHOLD MUST have a unique KEYID"* + - [x] *"Even if a KEYID is listed more than once in the "signatures" list a client MUST NOT count more than one verified SIGNATURE from that KEYID towards the THRESHOLD."* +- [x] *"Check for a rollback attack. The version number of the new root metadata (version N+1) MUST be exactly the version in the trusted root metadata (version N) incremented by one, that is precisely N+1."* +- [x] *"Check for a freeze attack. The expiration timestamp in the trusted root metadata file MUST be higher than the fixed update start time. If the trusted root metadata file has expired, abort the update cycle, report the potential freeze attack."* +- [x] *"If the timestamp and / or snapshot keys have been rotated, then delete the trusted timestamp and snapshot metadata files."* +- [ ] *"Set whether consistent snapshots are used as per the trusted root metadata file"* + +### Update Timestamp Role + +- Refer to [the corresponding TUF specification section](https://theupdateframework.github.io/specification/v1.0.33/index.html#update-timestamp). +- [x] *"Check for arbitrary Software Attack"* + - [x] *"The new timestamp metadata file MUST have been signed by:*" + - [x] *"a THRESHOLD of keys specified in the trusted root metadata file (version N)"* + - [x] *"When computing the THRESHOLD each KEY MUST only contribute one SIGNATURE."* + - [x] *"each SIGNATURE which is counted towards the THRESHOLD MUST have a unique KEYID"* + - [x] *"Even if a KEYID is listed more than once in the "signatures" list a client MUST NOT count more than one verified SIGNATURE from that KEYID towards the THRESHOLD."* +- [x] *"Check for a rollback attack."* + - [x] *"The version number of the trusted timestamp metadata file, if any, MUST be less than the version number of the new timestamp metadata file."* + - [x] *"In case they are equal, discard the new timestamp metadata and abort the update cycle. This is normal and it shouldn’t raise any error."* + - [x] *"The version number of the snapshot metadata file in the trusted timestamp metadata file, if any, MUST be less than or equal to its version number in the new timestamp metadata file. If not, discard the new timestamp metadata file, abort the update cycle, and report the failure."* +- [x] *"Check for a freeze attack. The expiration timestamp in the new timestamp metadata file MUST be higher than the fixed update start time. If so, the new timestamp metadata file becomes the trusted timestamp metadata file. If the new timestamp metadata file has expired, discard it, abort the update cycle, and report the potential freeze attack."* + + +### Update Snapshot Role + + +- Refer to [the corresponding TUF specification section](https://theupdateframework.github.io/specification/v1.0.33/index.html#update-snapshot). +- [x] *"Check against timestamp role’s snapshot hash. The hashes of the new snapshot metadata file MUST match the hashes, if any, listed in the trusted timestamp metadata."* +- [x] *"Check for arbitrary Software Attack"* + - [x] *"The new snapshot metadata file MUST have been signed by:*" + - [x] *"a THRESHOLD of keys specified in the trusted root metadata file (version N)"* + - [x] *"When computing the THRESHOLD each KEY MUST only contribute one SIGNATURE."* + - [x] *"each SIGNATURE which is counted towards the THRESHOLD MUST have a unique KEYID"* + - [x] *"Even if a KEYID is listed more than once in the "signatures" list a client MUST NOT count more than one verified SIGNATURE from that KEYID towards the THRESHOLD."* +- [x] *"The version number of the new snapshot metadata file MUST match the version number listed in the trusted timestamp metadata."* +- [x] *"Check for a rollback attack."* + - [x] *"The version number of the targets metadata file, and all delegated targets metadata files, if any, in the trusted snapshot metadata file, if any, MUST be less than or equal to its version number in the new snapshot metadata file."* + - [x] *"[A]ny targets metadata filename that was listed in the trusted snapshot metadata file, if any, MUST continue to be listed in the new snapshot metadata file."* +- [x] *"Check for a freeze attack. The expiration timestamp in the new snapshot metadata file MUST be higher than the fixed update start time. If so, the new snapshot metadata file becomes the trusted snapshot metadata file."* + +### Update Targets Role + +- Refer to [the corresponding TUF specification section](https://theupdateframework.github.io/specification/v1.0.33/index.html#update-targets). + +- [x] *"Check against snapshot role’s targets hash. The hashes of the new targets metadata file MUST match the hashes, if any, listed in the trusted snapshot metadata."* +- [x] *"Check for arbitrary Software Attack"* + - [x] *"The new targets metadata file MUST have been signed by[:]*" + - [x] *"a THRESHOLD of keys specified in the trusted root metadata file (version N)"* + - [x] *"When computing the THRESHOLD each KEY MUST only contribute one SIGNATURE."* + - [x] *"each SIGNATURE which is counted towards the THRESHOLD MUST have a unique KEYID"* + - [x] *"Even if a KEYID is listed more than once in the "signatures" list a client MUST NOT count more than one verified SIGNATURE from that KEYID towards the THRESHOLD."* +- [x] *"Check against snapshot role’s targets version. The version number of the new targets metadata file MUST match the version number listed in the trusted snapshot metadata."* +- [x] *"Check for a freeze attack. The expiration timestamp in the new targets metadata file MUST be higher than the fixed update start time."* +- [ ] *"Perform a pre-order depth-first search for metadata about the desired target, beginning with the top-level targets role."* + - [ ] *"If this role has been visited before, then skip this role (so that cycles in the delegation graph are avoided). Otherwise, if an application-specific maximum number of roles have been visited, then go to step § 5.7 Fetch target (so that attackers cannot cause the client to waste excessive bandwidth or time). Otherwise, if this role contains metadata about the desired target, then go to step § 5.7 Fetch target."* + - [ ] *"Otherwise, recursively search the list of delegations in order of appearance."* + - [x] *"If the current delegation is a terminating delegation, then jump to step § 5.7 Fetch target."* + - [] *"Otherwise, if the current delegation is a non-terminating delegation, continue processing the next delegation, if any. Stop the search, and jump to step § 5.7 Fetch target as soon as a delegation returns a result."* + +### Fetch Target + +- Refer to [the corresponding TUF specification section](https://theupdateframework.github.io/specification/v1.0.33/index.html#fetch-target). +- [x] *"Verify the desired target against its targets metadata."* +- [x] *"download the target (up to the number of bytes specified in the targets metadata)"* +- [x] *"verify that its hashes match the targets metadata"* + diff --git a/tuf-no-std/tests/1.registry.npmjs.org.json b/tuf-no-std/tests/1.registry.npmjs.org.json new file mode 100644 index 0000000..49d5d5f --- /dev/null +++ b/tuf-no-std/tests/1.registry.npmjs.org.json @@ -0,0 +1,23 @@ +{ + "signed": { + "_type": "targets", + "spec_version": "1.0", + "version": 1, + "expires": "2023-10-04T13:26:23Z", + "targets": { + "registry.npmjs.org/keys.json": { + "length": 1017, + "hashes": { + "sha256": "7a8ec9678ad824cdccaa7a6dc0961caf8f8df61bc7274189122c123446248426", + "sha512": "881a853ee92d8cf513b07c164fea36b22a7305c256125bdfffdc5c65a4205c4c3fc2b5bcc98964349167ea68d40b8cd02551fcaa870a30d4601ba1caf6f63699" + } + } + } + }, + "signatures": [ + { + "keyid": "a89d235ee2f298d757438c7473b11b0b7b42ff1a45f1dfaac4c014183d6f8c45", + "sig": "304402201acf213d8e3dea0a5f0c1757e4409a02661bcfab3278419307994ead07c36cb4022029986ed2241c630c4e45bf97e043c23220df7612e3718f921fe8a99c7515ad30" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/1.root.json b/tuf-no-std/tests/1.root.json new file mode 100644 index 0000000..a0fea1b --- /dev/null +++ b/tuf-no-std/tests/1.root.json @@ -0,0 +1,130 @@ +{ + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2021-12-18T13:28:12.99008-06:00", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + } + }, + "spec_version": "1.0", + "version": 1 + }, + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "30450221008a35d51da0f845301a5eac98ad0df00a934f59b709c1eaf81c86be734d9356f80220742942325599749800f52675f6efe124345980a2a636c0dc76f9caf9fc3123b0" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "3045022100ef9157ece2a09baec1eab80adfc00b04da20b1f9a0d1b47c5dabc4506719ef2c022074f72acd57398e4ddc8c2a5040df902961e9615dca48f3fbe38cbb506e500066" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "30450220420fdc9a09cd069b8b15fd8db9cedf7d0dee75871bd1cfee77c926d4120a770002210097553b5ad0d6b4a13902ed37509638bb63a9009f78230cd56c802909ffbfead7" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304502202aaf32e66f90752f658672b085ecfe45cc1ad31ee6cf5c9ad05f3267685f8d88022100b5df02acdaa371123db9d7a42219553fe079b230b168833e951be7ee56ded347" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "304402205d420c7d05c58980c1c9f7d221f53b5334aae27a447d2a91c2ceddd685269749022039ec83e51f8e1779d7f0142dfa4a5bbecfe327fc0b91b7416090fea2416fd53a" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/100.snapshot.json b/tuf-no-std/tests/100.snapshot.json new file mode 100644 index 0000000..a52e172 --- /dev/null +++ b/tuf-no-std/tests/100.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 100, + "expires": "2023-09-05T16:07:19Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022060ccd13742e29ab97ea2691ad9eb7b1cb7262b81ec499784113258b63d26f9330221009c7e1a24715c3aab61544f3fc323e300476a121a52e5a6ce04d26cb0ff2ea49f" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/101.snapshot.json b/tuf-no-std/tests/101.snapshot.json new file mode 100644 index 0000000..8d9d5e4 --- /dev/null +++ b/tuf-no-std/tests/101.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 101, + "expires": "2023-09-12T16:06:45Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304402206fe91c682f17ed6282996b3655d52fe3507293e6789ece8fb621b6147356292b0220442c5d1624978f08a966ef3c4acffe2c62ff20c77a1ec22ac2f5087150189f25" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/102.snapshot.json b/tuf-no-std/tests/102.snapshot.json new file mode 100644 index 0000000..9064022 --- /dev/null +++ b/tuf-no-std/tests/102.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 102, + "expires": "2023-09-19T16:06:58Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30450220695fb3ff8b2d92be534f894a0a4b48e52307af21c59670ea8c9829348999fe6b022100bada602e1dfdb43a08f0477e5bdfa4ed6ba5a8602a3e62857308a83dffadde26" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/103.snapshot.json b/tuf-no-std/tests/103.snapshot.json new file mode 100644 index 0000000..a99ccf3 --- /dev/null +++ b/tuf-no-std/tests/103.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 103, + "expires": "2023-09-26T16:07:01Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30460221009336ebe2d9a98152912113c46788756710c925a6eb7d1866ff3c9f9fd736a4a5022100d0b07ea6f072e8ea2f687e46695b3b7c9fa29a6a6440c90a53c0616a58bc24ac" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/104.snapshot.json b/tuf-no-std/tests/104.snapshot.json new file mode 100644 index 0000000..ca5af6c --- /dev/null +++ b/tuf-no-std/tests/104.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 104, + "expires": "2023-10-03T16:08:51Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304502200e0aaea2e86a8a0127283c479eb9ceee6c76b805f6f572f522b5824a3991358302210082696cfcab2e121d78727115f04f3e0ff36da8e12d96b24604ec0c555f2c8640" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/105.snapshot.json b/tuf-no-std/tests/105.snapshot.json new file mode 100644 index 0000000..2c172c9 --- /dev/null +++ b/tuf-no-std/tests/105.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 105, + "expires": "2023-10-10T16:08:34Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100f8a2f2f82bd6fd1d96378574ad65a59f742adf1361753e172786326f5b68f1b2022100eac1a0943901a2fafbff8383e0ef2dcf96a9f0a687bebf7436437f039a63ecd3" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/2.root.json b/tuf-no-std/tests/2.root.json new file mode 100644 index 0000000..386ebe6 --- /dev/null +++ b/tuf-no-std/tests/2.root.json @@ -0,0 +1,144 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2022-05-11T19:09:02.663975009Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 2 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/3.root.json b/tuf-no-std/tests/3.root.json new file mode 100644 index 0000000..8d69c51 --- /dev/null +++ b/tuf-no-std/tests/3.root.json @@ -0,0 +1,136 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100e7a80e4b03eb8768999d20f104925fd9149faf3f6f73ee80f8c2e8d5f998f48c022100d3f01eb8effee202a244e710dca09530b9c57c5e510ab35172bd5eddd373ccc8" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502200e45fde5cf750f8c533c4f259eb1469510600993b98ae2c3cb8f1922cda96e27022100f5151760d0ef0882a96c2531ccd9f5e4a7ff2b259d8eb34ead8bfdf60cb52fee" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304502205a7ebeac3617bfb1aca957a6f74d37a02f2854afa54e5103fb3c891bb25836db022100f06614ca8d21f968e45edc29f826d8dbeed07c51d4cb473a734a2036171900de" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2022-11-10T21:58:09.733402317Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 3 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/4.root.json b/tuf-no-std/tests/4.root.json new file mode 100644 index 0000000..0a055fc --- /dev/null +++ b/tuf-no-std/tests/4.root.json @@ -0,0 +1,144 @@ +{ + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2023-01-12T18:22:02Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04d2086b87dd8bc3562bde27465795aa0ad30307c0b1f83f21742e30d992cd2299554685462ec9186b782178cc8e8e227c90f8b5e5a436fffecffa88fb52f24f1b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 4 + }, + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100f7d4abde3d694fba01af172466629249a6743efd04c3999f958494842a7aee1f022100d19a295f9225247f17650fdb4ad50b99c2326700aadd0afaec4ae418941c7c59" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "3045022075ec28360b3e310db9d3de281a5286e37884aefd9f0b7193ad67c68ab6ee95a2022100aa08a93c58d74d9cb128cea765cae378efe86092f253b75fd427aede48ac7e22" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304502201de38b2a56a58ae046f26e3be8673063cdde8f8b6a8733bc025ebaf0e09569c50221008f8620c960fa6f9cb52b7c39ce84a5ac18224be4a876a35e1bc8f5d76aa24e86" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3044022070d86c3fbc3fb69783d54a451187e43776d97effe500c51f2558939c80ab2bb902201fb14ce51c6c4f40e8f2db792c3d56da18fe0c39499fa3fca9e841fc8bee17f1" + }, + { + "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90", + "sig": "3046022100aa1ff582569287b5160864e20bb343eff92dec316940cebe5742e47a56e8cabd0221009dc18bad12920a39b7427914ecb46e2ead58f17136935afbba488b7d6f3160ff" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/5.root.json b/tuf-no-std/tests/5.root.json new file mode 100644 index 0000000..7abd5fd --- /dev/null +++ b/tuf-no-std/tests/5.root.json @@ -0,0 +1,156 @@ +{ + "signed": { + "_type": "root", + "spec_version": "1.0", + "version": 5, + "expires": "2023-04-18T18:13:43Z", + "keys": { + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" + } + }, + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" + } + }, + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" + } + }, + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" + } + }, + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" + } + }, + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" + } + }, + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" + } + } + }, + "roles": { + "root": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" + ], + "threshold": 1 + } + }, + "consistent_snapshot": true + }, + "signatures": [ + { + "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" + }, + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" + }, + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" + }, + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" + }, + { + "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90", + "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/5.targets.json b/tuf-no-std/tests/5.targets.json new file mode 100644 index 0000000..3598578 --- /dev/null +++ b/tuf-no-std/tests/5.targets.json @@ -0,0 +1,125 @@ +{ + "signed": { + "_type": "targets", + "spec_version": "1.0", + "version": 5, + "expires": "2023-04-18T18:13:43Z", + "targets": { + "artifact.pub": { + "length": 177, + "hashes": { + "sha256": "59ebf97a9850aecec4bc39c1f5c1dc46e6490a6b5fd2a6cacdcac0c3a6fc4cbf", + "sha512": "308fd1d1d95d7f80aa33b837795251cc3e886792982275e062409e13e4e236ffc34d676682aa96fdc751414de99c864bf132dde71581fa651c6343905e3bf988" + }, + "custom": { + "sigstore": { + "status": "Active", + "usage": "Unknown" + } + } + }, + "ctfe.pub": { + "length": 177, + "hashes": { + "sha256": "7fcb94a5d0ed541260473b990b99a6c39864c1fb16f3f3e594a5a3cebbfe138a", + "sha512": "4b20747d1afe2544238ad38cc0cc3010921b177d60ac743767e0ef675b915489bd01a36606c0ff83c06448622d7160f0d866c83d20f0c0f44653dcc3f9aa0bd4" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/test", + "usage": "CTFE" + } + } + }, + "ctfe_2022.pub": { + "length": 178, + "hashes": { + "sha256": "270488a309d22e804eeb245493e87c667658d749006b9fee9cc614572d4fbbdc", + "sha512": "e83fa4f427b24ee7728637fad1b4aa45ebde2ba02751fa860694b1bb16059a490328f9985e51cc70e4d237545315a1bc866dc4fdeef2f6248d99cc7a6077bf85" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/2022", + "usage": "CTFE" + } + } + }, + "fulcio.crt.pem": { + "length": 744, + "hashes": { + "sha256": "f360c53b2e13495a628b9b8096455badcb6d375b185c4816d95a5d746ff29908", + "sha512": "0713252a7fd17f7f3ab12f88a64accf2eb14b8ad40ca711d7fe8b4ecba3b24db9e9dffadb997b196d3867b8f9ff217faf930d80e4dab4e235c7fc3f07be69224" + }, + "custom": { + "sigstore": { + "status": "Expired", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_intermediate_v1.crt.pem": { + "length": 789, + "hashes": { + "sha256": "f8cbecf186db7714624a5f4e99da31a917cbef70a94dd6921f5c3ca969dfe30a", + "sha512": "0f99f47dbc26c5f1e3cba0bfd9af4245a26e5cb735d6ef005792ec7e603f66fdb897de985973a6e50940ca7eff5e1849719e967b5ad2dac74a29115a41cf6f21" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_v1.crt.pem": { + "length": 740, + "hashes": { + "sha256": "f989aa23def87c549404eadba767768d2a3c8d6d30a8b793f9f518a8eafd2cf5", + "sha512": "f2e33a6dc208cee1f51d33bbea675ab0f0ced269617497985f9a0680689ee7073e4b6f8fef64c91bda590d30c129b3070dddce824c05bc165ac9802f0705cab6" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "rekor.pub": { + "length": 178, + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://rekor.sigstore.dev", + "usage": "Rekor" + } + } + } + } + }, + "signatures": [ + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "3045022100bf03c32b59f65285b91118172503c9f7e5f65fea0d4647f31adfb6cf18ed09db022069778e655e4198a3346ea9239dacb111571c7e7ed4c96d166ddce06306486a9c" + }, + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "30440220562f52b2243e66d8dff72dbf67a29faf82ad60ecbe0638acd4ab00338244f0b102206051db1fbe5a7815b4076096d5f8002c0dc1ecce8d9ef9d696cdacff50c7463a" + }, + { + "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "sig": "3045022100df19bbbabed7672c8e797152d6b97aa1f14fdcd6e10ce0e41703d5e7ad37c2e502200583577549f561079273460afe2b827b16d5e76a63616390bf956ee5f24d60eb" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "304502207d79f0ee8965f82c24fc5b96d6fbfa760b1f7192fd829a64a32ec03c579220310221008498a536dcc7aefd267875267f08cb27f8ae455dc6d8c53fe628e2fda2772dd4" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/53.snapshot.json b/tuf-no-std/tests/53.snapshot.json new file mode 100644 index 0000000..4b53ecc --- /dev/null +++ b/tuf-no-std/tests/53.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 53, + "expires": "2022-11-10T21:10:22Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30440220767eb9e8911edc8e2d4822d7f13d3adc03f32ab6388c3531935777fa33f7089e02202732bb45bc09801a9a547d834d1706f5fc89ccc8506d119a4303ab6af264a7f4" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/54.snapshot.json b/tuf-no-std/tests/54.snapshot.json new file mode 100644 index 0000000..3fd67fa --- /dev/null +++ b/tuf-no-std/tests/54.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 54, + "expires": "2022-11-21T15:59:09Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100975026a9594db91b921d6f1190306f38f06f55ca3393194eec896b64acfe7761022100fe9f9133ba25915262beba54074cd45a70d0e4b6da699e59d479af04aa34eddf" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/55.snapshot.json b/tuf-no-std/tests/55.snapshot.json new file mode 100644 index 0000000..e4efef8 --- /dev/null +++ b/tuf-no-std/tests/55.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 55, + "expires": "2022-11-29T17:02:57Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30450220148c56d2ccd33c80b529d02a2c11aa9effc793034829b1fe028b03482aeacf0d022100b3f729dd106451df9b8d6c3bc32bd4729fc5cd00c69af0ae37c38a027c13a703" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/56.snapshot.json b/tuf-no-std/tests/56.snapshot.json new file mode 100644 index 0000000..92d0bc9 --- /dev/null +++ b/tuf-no-std/tests/56.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 56, + "expires": "2022-12-07T23:40:52Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304402200712a5ebf1b62689573594dac0c145051bc75e5e2e1b3e19eae2df0034ed6a2c022044d3eda32870f54333604a2d6298c271645a6eec91f1964dd583e7bc5231d3b0" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/57.snapshot.json b/tuf-no-std/tests/57.snapshot.json new file mode 100644 index 0000000..b14c432 --- /dev/null +++ b/tuf-no-std/tests/57.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 57, + "expires": "2022-12-19T14:36:29Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100be618734bbebbc4d42fc94c067efd46aeaea417514cb294186812a6bd6f568560220191080395e4c99c4169f41981086df959f2f2ae46b52c009a4633b4e45d1d2a8" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/58.snapshot.json b/tuf-no-std/tests/58.snapshot.json new file mode 100644 index 0000000..d7b4fe9 --- /dev/null +++ b/tuf-no-std/tests/58.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 58, + "expires": "2022-12-22T00:10:10Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100f54aca630ec8f6ae230e000a852a62ea6a11b1147fd2e592c11f06265ea5af22022055ef211955f24b3086a7eaf7025398bb6f2a8026b436b0c60c6e7bda3285e4ed" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/59.snapshot.json b/tuf-no-std/tests/59.snapshot.json new file mode 100644 index 0000000..a2b7878 --- /dev/null +++ b/tuf-no-std/tests/59.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 59, + "expires": "2022-12-29T00:08:14Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022038dc30edbab3494a9a1325cbb162d0c6ed83f31439dc8d9d0a0d9621a11cfdc602210083f5a12fa4f63d669c098fe6de1f6a2c46e7bed2dfb6b757d58667a001ec277c" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/6.root.json b/tuf-no-std/tests/6.root.json new file mode 100644 index 0000000..0b2c06b --- /dev/null +++ b/tuf-no-std/tests/6.root.json @@ -0,0 +1,144 @@ +{ + "signed": { + "_type": "root", + "spec_version": "1.0", + "version": 6, + "expires": "2023-08-28T07:54:10Z", + "keys": { + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" + } + }, + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" + } + }, + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" + } + }, + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" + } + }, + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" + } + }, + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" + } + }, + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" + } + } + }, + "roles": { + "root": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" + ], + "threshold": 1 + } + }, + "consistent_snapshot": true + }, + "signatures": [ + { + "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "sig": "3044022079941eab7035ffd603354ee9a072ad87ad24e084f2aa52a718f76b21545d90190220368a65bb4ac83a9938885f5bba6a0b9a25c9979c85d85840497a95e47466eafb" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "3045022100ca33d35657c55b93c827ecad61be61e6d91da886d413f5083894a70d6e9af1cd022049d2b8b50d34a4e48cb3832a17a82c1ec1ae6b61af2a6db6e7d1c63d81a0dae7" + }, + { + "keyid": "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "sig": "304402207875857b20d8258e0c888e55516cb50593746543cd3c34c9743efb2921cb2a660220127107a67b585e67d3df0538a6be1f5d4834857d1d88da9f8a6b8b8ac8998904" + }, + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "304502205c7b76ad222ffe16fed152f5bbf1c18b3df4814bf93703fea4605ae335914953022100a9d187ee02a4babe12b1646b572171bac60b23b0846ff3f067ded075194b549c" + }, + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "30440220724e672fd7a2dbd338dfea683712a77bc1579ae5061dbc501d498ade02ea3aeb022012758bd3f1d4d245d92a692d26f743ad7a1f9af0982d1983a8619186c1fbcdd4" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/6.targets.json b/tuf-no-std/tests/6.targets.json new file mode 100644 index 0000000..d096a05 --- /dev/null +++ b/tuf-no-std/tests/6.targets.json @@ -0,0 +1,136 @@ +{ + "signed": { + "_type": "targets", + "spec_version": "1.0", + "version": 6, + "expires": "2023-08-28T07:54:10Z", + "targets": { + "artifact.pub": { + "length": 177, + "hashes": { + "sha256": "59ebf97a9850aecec4bc39c1f5c1dc46e6490a6b5fd2a6cacdcac0c3a6fc4cbf", + "sha512": "308fd1d1d95d7f80aa33b837795251cc3e886792982275e062409e13e4e236ffc34d676682aa96fdc751414de99c864bf132dde71581fa651c6343905e3bf988" + }, + "custom": { + "sigstore": { + "status": "Active", + "usage": "Unknown" + } + } + }, + "ctfe.pub": { + "length": 177, + "hashes": { + "sha256": "7fcb94a5d0ed541260473b990b99a6c39864c1fb16f3f3e594a5a3cebbfe138a", + "sha512": "4b20747d1afe2544238ad38cc0cc3010921b177d60ac743767e0ef675b915489bd01a36606c0ff83c06448622d7160f0d866c83d20f0c0f44653dcc3f9aa0bd4" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/test", + "usage": "CTFE" + } + } + }, + "ctfe_2022.pub": { + "length": 178, + "hashes": { + "sha256": "270488a309d22e804eeb245493e87c667658d749006b9fee9cc614572d4fbbdc", + "sha512": "e83fa4f427b24ee7728637fad1b4aa45ebde2ba02751fa860694b1bb16059a490328f9985e51cc70e4d237545315a1bc866dc4fdeef2f6248d99cc7a6077bf85" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/2022", + "usage": "CTFE" + } + } + }, + "fulcio.crt.pem": { + "length": 744, + "hashes": { + "sha256": "f360c53b2e13495a628b9b8096455badcb6d375b185c4816d95a5d746ff29908", + "sha512": "0713252a7fd17f7f3ab12f88a64accf2eb14b8ad40ca711d7fe8b4ecba3b24db9e9dffadb997b196d3867b8f9ff217faf930d80e4dab4e235c7fc3f07be69224" + }, + "custom": { + "sigstore": { + "status": "Expired", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_intermediate_v1.crt.pem": { + "length": 789, + "hashes": { + "sha256": "f8cbecf186db7714624a5f4e99da31a917cbef70a94dd6921f5c3ca969dfe30a", + "sha512": "0f99f47dbc26c5f1e3cba0bfd9af4245a26e5cb735d6ef005792ec7e603f66fdb897de985973a6e50940ca7eff5e1849719e967b5ad2dac74a29115a41cf6f21" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_v1.crt.pem": { + "length": 740, + "hashes": { + "sha256": "f989aa23def87c549404eadba767768d2a3c8d6d30a8b793f9f518a8eafd2cf5", + "sha512": "f2e33a6dc208cee1f51d33bbea675ab0f0ced269617497985f9a0680689ee7073e4b6f8fef64c91bda590d30c129b3070dddce824c05bc165ac9802f0705cab6" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "rekor.pub": { + "length": 178, + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://rekor.sigstore.dev", + "usage": "Rekor" + } + } + }, + "trusted_root.json": { + "length": 4567, + "hashes": { + "sha256": "cec894ad77f79b1cb324150f6363012bcef7492954f3ab9134f932e6aa2e2e20", + "sha512": "08be2fd75c19e654caad30852847c566f97e6245f2bbcc54d347d6bdec7e879135e3395b5633b9e3b85d739fdb9b4eb8c09ddc70495792bc2ea65c8caf770d27" + } + } + } + }, + "signatures": [ + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "3044022061696eb6f8b51dd576b283f1326721fc1287ed87301f96b2b694e711efd0308702205a8f8b30c093032400d0e8a2d16388cebc3ad36fddd78baed7e8f6199a95aec8" + }, + { + "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "sig": "3046022100b1c637be9b9ca306538a686f24943fa1bceb0e6efaeb8d8c66182502a6e1a651022100a9c002f701ecf37f7fbf493bd2a97751f1280d9786fb34fafa13b78182f59822" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "30450221009b51e35eb5f6fbe664d8d9f2131a0293d6bf4e9128debba563892d17e51c4132022056cf3a48a482dc09d56b78ffacf13a5045054b7861b154239fc6b78c44b282e7" + }, + { + "keyid": "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "sig": "3045022100c508e1c2ac28ff5beb50aa2868f55fcba73fe17ea02d73d76b334778a65aba8102203ffd85878fd0f8ec78f8124a5229720c2d91536608d5487022fe502d6e4d7649" + }, + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "3045022100968006fba63357bfbe81a6bd43228f46586fd5a15cd2519fc00d629636687ef1022002b2766ee0d845d48db09a8787d375d535d10573f4fc227b06a8b4ec46b883f4" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/60.snapshot.json b/tuf-no-std/tests/60.snapshot.json new file mode 100644 index 0000000..7a81803 --- /dev/null +++ b/tuf-no-std/tests/60.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 60, + "expires": "2023-01-05T00:08:27Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022064943e143665125a93e0646bd24516f7d8b950caf644a13a129d415d5a557f8d022100d6409dbaafb5d8698974dfe987dc84d6c49a364b1bd20641bfeebb002285e18d" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/61.snapshot.json b/tuf-no-std/tests/61.snapshot.json new file mode 100644 index 0000000..1589f1c --- /dev/null +++ b/tuf-no-std/tests/61.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 61, + "expires": "2023-01-12T00:07:59Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30440220437f105a861bf1c828c9fd016557688e095e630b61e3f1707961300e63f0894c022012f7f4f292e2f01ca32351571c0cf3e2ef20e7f3ea69b00dba539d45d6f1ea0b" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/62.snapshot.json b/tuf-no-std/tests/62.snapshot.json new file mode 100644 index 0000000..059b5a9 --- /dev/null +++ b/tuf-no-std/tests/62.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 62, + "expires": "2023-01-19T01:12:57Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100ec02beec8d301be4f160045deac81122a6c3a84fd7df825293118db93ebb80c6022100cbfe25f7034c834d0d06a734e7c33810a8797fa9b0f3448674bf6289b88d7250" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/63.snapshot.json b/tuf-no-std/tests/63.snapshot.json new file mode 100644 index 0000000..06a5a1c --- /dev/null +++ b/tuf-no-std/tests/63.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 63, + "expires": "2023-01-22T00:08:42Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100c829b6534ee82724a771ab77791d794ea210cf33549ccdab103795adc0875bdf0220577bbd2b892481d5da8fa0fd32238ad07b25995c31e1222108b021c400ff4813" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/64.snapshot.json b/tuf-no-std/tests/64.snapshot.json new file mode 100644 index 0000000..39726ce --- /dev/null +++ b/tuf-no-std/tests/64.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 64, + "expires": "2023-01-29T00:08:48Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30450221008cd2c0e3aa7cf586237d0ad60651db27e0fd7369f6939a6b11ebe5006283f44a02200be12170e98f1e0e7b4796c514bf7fda2a174aaeb32dae8c6e44a87d2bc3d5aa" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/65.snapshot.json b/tuf-no-std/tests/65.snapshot.json new file mode 100644 index 0000000..099fef6 --- /dev/null +++ b/tuf-no-std/tests/65.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 65, + "expires": "2023-02-05T00:08:38Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30440220190e4f5c1fa91fcb4da0598dae9606d1145ee26dde02038383b19f93c7db2aa602205c566db8f3c786ab572db379fa15a49ae8e3626d666e76cd95ab07eb9d05e4b6" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/66.snapshot.json b/tuf-no-std/tests/66.snapshot.json new file mode 100644 index 0000000..5009336 --- /dev/null +++ b/tuf-no-std/tests/66.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 66, + "expires": "2023-02-12T00:08:53Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3044022069a7ea2db1987ee76884ed5350840d95909b9c655c8f62deccb6e3ac23dde58a02207344d3fa328df12d9a33acf62076a33776965569e2d1f685273343dfb7efa0a2" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/67.snapshot.json b/tuf-no-std/tests/67.snapshot.json new file mode 100644 index 0000000..5800e64 --- /dev/null +++ b/tuf-no-std/tests/67.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 67, + "expires": "2023-02-19T00:08:44Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100b39e717d8629d1c291ab2d77e1c16facbda14e7fc9132910b240f0e4f4efab79022003becd79ee922bb2f76c38c84fd9ad1688578737333799307e2013fe3631860c" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/68.snapshot.json b/tuf-no-std/tests/68.snapshot.json new file mode 100644 index 0000000..7b32788 --- /dev/null +++ b/tuf-no-std/tests/68.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 68, + "expires": "2023-02-22T00:09:16Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100c1304df9ba6f3d1c28b7f5fd31e2f874b29f7b163bffe3f096847594924f1aeb022100e33ee32d9334668ee2af6b609ca58e1b158b5472b7e04be5c8207b783138a838" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/69.snapshot.json b/tuf-no-std/tests/69.snapshot.json new file mode 100644 index 0000000..4a23bf3 --- /dev/null +++ b/tuf-no-std/tests/69.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 69, + "expires": "2023-03-01T00:07:52Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3044022056f506140374dda69b810c22673b3facb5ed298a5168cc935abb76854ac70aa30220530e72f5b2bb824e772a1d14ebc8137ff4677792f1ae861b115b5cc06426d251" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/7.root.json b/tuf-no-std/tests/7.root.json new file mode 100644 index 0000000..c3ea9cb --- /dev/null +++ b/tuf-no-std/tests/7.root.json @@ -0,0 +1,140 @@ +{ + "signed": { + "_type": "root", + "spec_version": "1.0", + "version": 7, + "expires": "2023-10-04T13:08:11Z", + "keys": { + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" + } + }, + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" + } + }, + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" + } + }, + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" + } + }, + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" + } + }, + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" + } + }, + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" + } + } + }, + "roles": { + "root": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" + ], + "threshold": 1 + } + }, + "consistent_snapshot": true + }, + "signatures": [ + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "3046022100c0610c0055ce5c4a52d054d7322e7b514d55baf44423d63aa4daa077cc60fd1f022100a097f2803f090fb66c42ead915a2c46ebe7db53a32bf18f2188275cc936f8bdd" + }, + { + "keyid": "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "sig": "304502203134f0468810299d5493a867c40630b341296b92e59c29821311d353343bb3a4022100e667ae3d304e7e3da0894c7425f6b9ecd917106841280e5cf6f3496ad5f8f68e" + }, + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "3045022037fe5f45426f21eaaf4730d2136f2b1611d6379688f79b9d1e3f61719997135c022100b63b022d7b79d4694b96f416d88aa4d7b1a3bff8a01f4fb51e0f42137c7d2d06" + }, + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "3044022007cc8fcc4940809f2751ad5b535f4c5f53f5b4952f5b5696b09668e743306ac1022006dfcdf94e94c92163eeb1b47796db62cedaa730aa13aa61b573fe23714730f2" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/7.targets.json b/tuf-no-std/tests/7.targets.json new file mode 100644 index 0000000..c63cce9 --- /dev/null +++ b/tuf-no-std/tests/7.targets.json @@ -0,0 +1,160 @@ +{ + "signed": { + "_type": "targets", + "spec_version": "1.0", + "version": 7, + "expires": "2023-10-04T13:26:23Z", + "targets": { + "artifact.pub": { + "length": 177, + "hashes": { + "sha256": "59ebf97a9850aecec4bc39c1f5c1dc46e6490a6b5fd2a6cacdcac0c3a6fc4cbf", + "sha512": "308fd1d1d95d7f80aa33b837795251cc3e886792982275e062409e13e4e236ffc34d676682aa96fdc751414de99c864bf132dde71581fa651c6343905e3bf988" + }, + "custom": { + "sigstore": { + "status": "Active", + "usage": "Unknown" + } + } + }, + "ctfe.pub": { + "length": 177, + "hashes": { + "sha256": "7fcb94a5d0ed541260473b990b99a6c39864c1fb16f3f3e594a5a3cebbfe138a", + "sha512": "4b20747d1afe2544238ad38cc0cc3010921b177d60ac743767e0ef675b915489bd01a36606c0ff83c06448622d7160f0d866c83d20f0c0f44653dcc3f9aa0bd4" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/test", + "usage": "CTFE" + } + } + }, + "ctfe_2022.pub": { + "length": 178, + "hashes": { + "sha256": "270488a309d22e804eeb245493e87c667658d749006b9fee9cc614572d4fbbdc", + "sha512": "e83fa4f427b24ee7728637fad1b4aa45ebde2ba02751fa860694b1bb16059a490328f9985e51cc70e4d237545315a1bc866dc4fdeef2f6248d99cc7a6077bf85" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/2022", + "usage": "CTFE" + } + } + }, + "fulcio.crt.pem": { + "length": 744, + "hashes": { + "sha256": "f360c53b2e13495a628b9b8096455badcb6d375b185c4816d95a5d746ff29908", + "sha512": "0713252a7fd17f7f3ab12f88a64accf2eb14b8ad40ca711d7fe8b4ecba3b24db9e9dffadb997b196d3867b8f9ff217faf930d80e4dab4e235c7fc3f07be69224" + }, + "custom": { + "sigstore": { + "status": "Expired", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_intermediate_v1.crt.pem": { + "length": 789, + "hashes": { + "sha256": "f8cbecf186db7714624a5f4e99da31a917cbef70a94dd6921f5c3ca969dfe30a", + "sha512": "0f99f47dbc26c5f1e3cba0bfd9af4245a26e5cb735d6ef005792ec7e603f66fdb897de985973a6e50940ca7eff5e1849719e967b5ad2dac74a29115a41cf6f21" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_v1.crt.pem": { + "length": 740, + "hashes": { + "sha256": "f989aa23def87c549404eadba767768d2a3c8d6d30a8b793f9f518a8eafd2cf5", + "sha512": "f2e33a6dc208cee1f51d33bbea675ab0f0ced269617497985f9a0680689ee7073e4b6f8fef64c91bda590d30c129b3070dddce824c05bc165ac9802f0705cab6" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "rekor.pub": { + "length": 178, + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://rekor.sigstore.dev", + "usage": "Rekor" + } + } + }, + "trusted_root.json": { + "length": 7014, + "hashes": { + "sha256": "4364d7724c04cc912ce2a6c45ed2610e8d8d1c4dc857fb500292738d4d9c8d2c", + "sha512": "fdebade075c4840d40f1806a14d0660ae1d22f47c0516abc4141e09f4ddf6ee6f4dbfbf08a7025bea10a4b8794658a4cd8ebb1024b963f239a9bfe02c2057fc6" + } + } + }, + "delegations": { + "keys": { + "a89d235ee2f298d757438c7473b11b0b7b42ff1a45f1dfaac4c014183d6f8c45": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoLrh0jmOfHWLwsyo/4oGbldF91WV\nfXvxVlDhW8fZwP/3vTnliBkDp5sH8/Dpm1SBOHkqENVt1+4Un/sFtl2zAQ==\n-----END PUBLIC KEY-----\n" + } + } + }, + "roles": [ + { + "name": "registry.npmjs.org", + "keyids": [ + "a89d235ee2f298d757438c7473b11b0b7b42ff1a45f1dfaac4c014183d6f8c45" + ], + "threshold": 1, + "terminating": true, + "paths": [ + "registry.npmjs.org/*" + ] + } + ] + } + }, + "signatures": [ + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "3045022100eeb820ad0b0aabb0f6bf68159d7a9cbd24d1c4633f14317f9944fcb3bb2e1ff802207f92fb20b84df6bcf0c3c2f39fa089238bbee9ff12490c555b694421a45e8d5d" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "304502202984e536e9442c9f5a14700195b481fb0b3255e4572734700c5ff527b1135f88022100be2f4947b34a02e391417c96ffab8643476164fc44fa258bfe0133ab49858f29" + }, + { + "keyid": "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "sig": "3045022100d757e9268801bb1eb4f883abd570dae52a3201c622b4e7e5a30a8b42f3e2f54b02206663b846a8d13d95497d651334dbb754587a14c274fadd91ca796733bc467b95" + }, + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "3045022054d7fb7d70a80f8f4021f090307ada7a196a32f4794954baf405475b38e68d45022100d85fde60eecb2003e4eb329e1b68f22d33dbe11a2293155dad88209250b25b2c" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/70.snapshot.json b/tuf-no-std/tests/70.snapshot.json new file mode 100644 index 0000000..a25aa29 --- /dev/null +++ b/tuf-no-std/tests/70.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 70, + "expires": "2023-03-08T00:08:32Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100cc2678b0de41677ccd7653054f39630c923c4555653e28e67a34fecb632d51c6022021590f345a3e6e542f3099b594eccb1de02b635fbaee154269fa10e8ac553112" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/71.snapshot.json b/tuf-no-std/tests/71.snapshot.json new file mode 100644 index 0000000..3d24343 --- /dev/null +++ b/tuf-no-std/tests/71.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 71, + "expires": "2023-03-15T00:09:04Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3044022058c63d131e187d2f810af0933ecad0275be9723ab639f76a307a8b4587101c5502207c80d639b8b1ec83a737d6abfb495016690d20ec01e4003f6442d90c3c6eede1" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/72.snapshot.json b/tuf-no-std/tests/72.snapshot.json new file mode 100644 index 0000000..e6c5003 --- /dev/null +++ b/tuf-no-std/tests/72.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 72, + "expires": "2023-03-22T00:09:39Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4188, + "hashes": { + "sha256": "5dbc142fcda89c914175b4e8570a2745d41f8ff799625b8890e6e56e009038ca", + "sha512": "e9397f3c1b84c7c7e52f91e4e62409c66af42bde74f93e12005054ee5fc00a1811685306276bea115dc1e4679cd8e6d9aeb49115e9493872b0c1c9308f93714a" + }, + "version": 5 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304502205a3364cefd870314d1bd7f4a9a97c3198e985964a7ed81cd08543a8feef33279022100f3a57fa9531d15d4cdda1aae416d41a12259817803b501fbab691c42d2fce0ca" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/73.snapshot.json b/tuf-no-std/tests/73.snapshot.json new file mode 100644 index 0000000..3eb55f7 --- /dev/null +++ b/tuf-no-std/tests/73.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 73, + "expires": "2023-03-23T12:12:10Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4737, + "hashes": { + "sha256": "79698024b773e7c669b8c5def0031fdc7cd2ab7785d80f7f72a7495472f63218", + "sha512": "e19872e801ccefee869177d72a6f929197fd02faaa823fbd0f0bb6a0833ef7246040f90c313a271fbb2d29ac7ca5cab7aa09d422a9ec85950f2ad297fc455915" + }, + "version": 6 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30440220138586309855cb19cce0036a72c2119605eee68577eea2bd08c1d8c141e582d502207bc523dce58809c089af8b713b4f577965ba59c759761e899c4ccde5852c8c36" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/74.snapshot.json b/tuf-no-std/tests/74.snapshot.json new file mode 100644 index 0000000..bbbf4c6 --- /dev/null +++ b/tuf-no-std/tests/74.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 74, + "expires": "2023-03-29T00:08:35Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4737, + "hashes": { + "sha256": "79698024b773e7c669b8c5def0031fdc7cd2ab7785d80f7f72a7495472f63218", + "sha512": "e19872e801ccefee869177d72a6f929197fd02faaa823fbd0f0bb6a0833ef7246040f90c313a271fbb2d29ac7ca5cab7aa09d422a9ec85950f2ad297fc455915" + }, + "version": 6 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100c9c6338ee2a24ecea7e2e04d2c1d5b99838c506dcef20ec6f6ec0241fe41286802200f092258a257f519871727ca43fd29f08e48cd8539638b90fb051273cf77d515" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/75.snapshot.json b/tuf-no-std/tests/75.snapshot.json new file mode 100644 index 0000000..3d74ce8 --- /dev/null +++ b/tuf-no-std/tests/75.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 75, + "expires": "2023-04-05T00:08:33Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4737, + "hashes": { + "sha256": "79698024b773e7c669b8c5def0031fdc7cd2ab7785d80f7f72a7495472f63218", + "sha512": "e19872e801ccefee869177d72a6f929197fd02faaa823fbd0f0bb6a0833ef7246040f90c313a271fbb2d29ac7ca5cab7aa09d422a9ec85950f2ad297fc455915" + }, + "version": 6 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100d334e319c67af10e466d743e5ce2098a895290ac3e3d80c20dd180c98ffe8b00022043a7695ea314f53d7a0d27a102c2c28a2e4772cfb3e49c9da16756af0ddd4e09" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/76.snapshot.json b/tuf-no-std/tests/76.snapshot.json new file mode 100644 index 0000000..7bf4e35 --- /dev/null +++ b/tuf-no-std/tests/76.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 76, + "expires": "2023-04-12T00:06:27Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4737, + "hashes": { + "sha256": "79698024b773e7c669b8c5def0031fdc7cd2ab7785d80f7f72a7495472f63218", + "sha512": "e19872e801ccefee869177d72a6f929197fd02faaa823fbd0f0bb6a0833ef7246040f90c313a271fbb2d29ac7ca5cab7aa09d422a9ec85950f2ad297fc455915" + }, + "version": 6 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100dde566254e256db7c69fd03eb5059a1a61c1dfa07a8de8deef613838c08bb665022100d8c9350eaa72ce42df5f7a570aa2ee6d2dc51868520c30279cbd963bcf4209d3" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/77.snapshot.json b/tuf-no-std/tests/77.snapshot.json new file mode 100644 index 0000000..2e31457 --- /dev/null +++ b/tuf-no-std/tests/77.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 77, + "expires": "2023-04-19T00:08:00Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4737, + "hashes": { + "sha256": "79698024b773e7c669b8c5def0031fdc7cd2ab7785d80f7f72a7495472f63218", + "sha512": "e19872e801ccefee869177d72a6f929197fd02faaa823fbd0f0bb6a0833ef7246040f90c313a271fbb2d29ac7ca5cab7aa09d422a9ec85950f2ad297fc455915" + }, + "version": 6 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304502203c60c26b377a3b7e33ec28875ec19370c453d1c7305f658ba28fae33d95d932d022100edc042951938688e490e8a344390aae0fafc530f10cecb6b7864cb6c4a89b504" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/78.snapshot.json b/tuf-no-std/tests/78.snapshot.json new file mode 100644 index 0000000..ab6974e --- /dev/null +++ b/tuf-no-std/tests/78.snapshot.json @@ -0,0 +1,56 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 78, + "expires": "2023-04-22T00:07:04Z", + "meta": { + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 4737, + "hashes": { + "sha256": "79698024b773e7c669b8c5def0031fdc7cd2ab7785d80f7f72a7495472f63218", + "sha512": "e19872e801ccefee869177d72a6f929197fd02faaa823fbd0f0bb6a0833ef7246040f90c313a271fbb2d29ac7ca5cab7aa09d422a9ec85950f2ad297fc455915" + }, + "version": 6 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304502207f924837ad510a57a6760723ca46b964d7bc3a5a823de424e377906646b113d3022100a40d3cef790705f5bdeed93f0da28133c7063593758d9a2f6e3458bd4b563db2" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/79.snapshot.json b/tuf-no-std/tests/79.snapshot.json new file mode 100644 index 0000000..006d694 --- /dev/null +++ b/tuf-no-std/tests/79.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 79, + "expires": "2023-04-26T05:54:24Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304402204c98136370eb818742f2edca6f5eaaa9c210f5b6c09b06a32357dccdba40d33102201c970fea42ae3a46f5cf82968c44066043bd8c85b0b67d73926dbf37ea161df2" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/80.snapshot.json b/tuf-no-std/tests/80.snapshot.json new file mode 100644 index 0000000..32dd5d0 --- /dev/null +++ b/tuf-no-std/tests/80.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 80, + "expires": "2023-05-02T16:15:56Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100c2ee8a19903b6eaaade0795d53ef56d944659f70cde8e698a8ea3e505fb622c1022100ee4af7afa8f1831580e767cfcf568b6378082fd790ed92ed2b6630b71e932eb7" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/81.snapshot.json b/tuf-no-std/tests/81.snapshot.json new file mode 100644 index 0000000..0233de4 --- /dev/null +++ b/tuf-no-std/tests/81.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 81, + "expires": "2023-05-06T00:06:37Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304402202484c66f0dd45dc99a9dd7ec78e46e4eeaf255ccf7a203ccfe55967a6aed532d022079685790ce2f6ef8916d67c3ee3e140af68b9f2b12c9cfe3e10109813ba87fb5" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/82.snapshot.json b/tuf-no-std/tests/82.snapshot.json new file mode 100644 index 0000000..eaaca1a --- /dev/null +++ b/tuf-no-std/tests/82.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 82, + "expires": "2023-05-13T00:06:59Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100ce1e8c5262c35b375c16a45474c62e957bdc592f3abf6b4bcb5f2f23059e4bc5022100b238ab12c46c2a6c49d00a8224e5c6726a0fcbdc63bc76a82dc17df3765286cf" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/83.snapshot.json b/tuf-no-std/tests/83.snapshot.json new file mode 100644 index 0000000..f98bc78 --- /dev/null +++ b/tuf-no-std/tests/83.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 83, + "expires": "2023-05-20T00:06:32Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3044022002aff3ecdbf751886222e1f5c562de3fc5e90726685ddb82a6a30c20014101260220790576ec4eff2cfeb640d5e81e939f5e2d836ae083a68c4ca6f215e2c094b043" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/84.snapshot.json b/tuf-no-std/tests/84.snapshot.json new file mode 100644 index 0000000..5cb83df --- /dev/null +++ b/tuf-no-std/tests/84.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 84, + "expires": "2023-05-22T00:07:24Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100c907199023ea376a3eb432e0ddff75247f7906c11ab5513797eac623350cab0d02207e30c2ff78e85d3312b30370e07ea9571fe896ca5b2ef3973d51162d90868ee5" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/85.snapshot.json b/tuf-no-std/tests/85.snapshot.json new file mode 100644 index 0000000..7e3fd20 --- /dev/null +++ b/tuf-no-std/tests/85.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 85, + "expires": "2023-05-29T16:02:03Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100ec501e5ad6335320705a673e8d27ead029378267ee1278f7d74f92714d54c6c3022100b5b25e58eaf5d8aa533916fd3f0d938a8169c060db3aea0fbf3e8e59c14c7da4" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/86.snapshot.json b/tuf-no-std/tests/86.snapshot.json new file mode 100644 index 0000000..6d6db68 --- /dev/null +++ b/tuf-no-std/tests/86.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 86, + "expires": "2023-06-05T16:07:24Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30440220059a3e2653a888ec3a249d0561167d6ff301efab5777e514c2ed70f859a7d01a0220259f6bf0c8cd34444c5a0a96858ea30e2b646357ff06f3cb389f761408ce3cea" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/87.snapshot.json b/tuf-no-std/tests/87.snapshot.json new file mode 100644 index 0000000..43f2b86 --- /dev/null +++ b/tuf-no-std/tests/87.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 87, + "expires": "2023-06-12T16:02:17Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100a70cceb25c5f93bfb0bf6861255bcc5abc776b1b8d51c1b764efa3d5d15d629d022100d662773a81c824c0f5d2bc1431caedf5289609836c7daf0cc962f2306920defb" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/88.snapshot.json b/tuf-no-std/tests/88.snapshot.json new file mode 100644 index 0000000..87df2ce --- /dev/null +++ b/tuf-no-std/tests/88.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 88, + "expires": "2023-06-19T16:02:51Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100f5591102af8e08a6d27ad9ea821c0db14e0df2fd37200af8e2aac79219bc4dea02207a5e98247f8b3da8656ae2ac4b80910bef119b66f8bd9b980312c58c463f2219" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/89.snapshot.json b/tuf-no-std/tests/89.snapshot.json new file mode 100644 index 0000000..f2b70f7 --- /dev/null +++ b/tuf-no-std/tests/89.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 89, + "expires": "2023-06-26T16:01:55Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3044022012187d7abce242e7feac4818c770630ee6d799b54f923098147946058d69f712022037b9958c15a058f4890d3b4c54420488bb9a52562cd42916c54073fa4fa7d2eb" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/90.snapshot.json b/tuf-no-std/tests/90.snapshot.json new file mode 100644 index 0000000..8d87a62 --- /dev/null +++ b/tuf-no-std/tests/90.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 90, + "expires": "2023-07-03T16:03:08Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100dd3b064b0da1653ebc8f2084473c0c4f89431e544e7e9f12492b8c1578719d2c022100ec60cedb17835da34740f6cf3408a5f0175c1a7601b50081e93f6fc95da21bbd" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/91.snapshot.json b/tuf-no-std/tests/91.snapshot.json new file mode 100644 index 0000000..4ea3a91 --- /dev/null +++ b/tuf-no-std/tests/91.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 91, + "expires": "2023-07-10T16:02:00Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "30440220773c14c5c76caf7f3b02b0bfd19451d629625132821aced5ad9b5f88bd83c82b02201ebae49d90aa95cf05e60eb211316062abbb9d1d929717410414be6575fb658e" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/92.snapshot.json b/tuf-no-std/tests/92.snapshot.json new file mode 100644 index 0000000..5ce6f3c --- /dev/null +++ b/tuf-no-std/tests/92.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 92, + "expires": "2023-07-17T16:01:58Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304402204e4a84fa9cf115ea8df23c523efdad10556f083c599f99bcbed8357c9e6ef10902202940758be7718dc09944ba82a1b7626abe61f135608ebee4f7b4c36a04c508d8" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/93.snapshot.json b/tuf-no-std/tests/93.snapshot.json new file mode 100644 index 0000000..ec70850 --- /dev/null +++ b/tuf-no-std/tests/93.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 93, + "expires": "2023-07-24T16:02:12Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100834c88c5e4da6510e9ccae8340191ffc6ec9edaf978ecd6d5842f81dbb942412022100e5fef7dbc388fe6ee09a922346780ac34d7832655a8cdd35d5338e48c0711f07" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/94.snapshot.json b/tuf-no-std/tests/94.snapshot.json new file mode 100644 index 0000000..6722bcb --- /dev/null +++ b/tuf-no-std/tests/94.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 94, + "expires": "2023-07-31T16:01:54Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3044022061e74ee2e3ead8a1571b67cef13f03cd1b269c0009ee61c47146430cc7e53de8022001daf18d96510630c8360749e238ea62186b9ce1652011fd2898af4390e40540" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/95.snapshot.json b/tuf-no-std/tests/95.snapshot.json new file mode 100644 index 0000000..b8c0bf9 --- /dev/null +++ b/tuf-no-std/tests/95.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 95, + "expires": "2023-08-07T16:01:59Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100b55e57cd19dedf2ec1e59849704b5ae839fa24aafcd6b06281926137daf178bc02201149c08507cf049dd4bc23874853889305a74f714e30b26a98f7350cfb0e61a1" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/96.snapshot.json b/tuf-no-std/tests/96.snapshot.json new file mode 100644 index 0000000..51edcce --- /dev/null +++ b/tuf-no-std/tests/96.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 96, + "expires": "2023-08-14T16:01:40Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3044022023fce9e7c4626d222afd4fe0e2ab1d8f364abcb09b7b9b04cd30e0d363288fca02203e791a4cb731d873018cba209f80a076e22e3c8d40a1c912a36382b0ea75b3b5" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/97.snapshot.json b/tuf-no-std/tests/97.snapshot.json new file mode 100644 index 0000000..6fb45d2 --- /dev/null +++ b/tuf-no-std/tests/97.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 97, + "expires": "2023-08-15T16:07:13Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304402200de4c9766e8c95c1648fea7e76972da549f273b1827bcd87d67007ede26f4db102206b96ea81507052b08c44307b5382806d560e383b5202e95b5282669a4d6e21f3" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/98.snapshot.json b/tuf-no-std/tests/98.snapshot.json new file mode 100644 index 0000000..6296f83 --- /dev/null +++ b/tuf-no-std/tests/98.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 98, + "expires": "2023-08-22T16:06:39Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3045022100e09ed2c67f70b8050e9f1bb5e93d20568db2d2e5e06784f5d4873ef31b4db549022070d2eb5648a1416f3a5bb1f6c7e5ab37c8d80d05ac7f51922ad81eaed87fb140" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/99.snapshot.json b/tuf-no-std/tests/99.snapshot.json new file mode 100644 index 0000000..a31fefd --- /dev/null +++ b/tuf-no-std/tests/99.snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 99, + "expires": "2023-08-29T16:06:57Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "304502202b8ef04b9b4584d5d99fece32d801a01d8eb2d8a4d26c17bf2837ab4976577f2022100a01385d9e06bfe28f9859272ac4bb087a44f9d22ab7e02655766399dc5aae557" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/generated_data/ed25519_metadata/root_with_ed25519.json b/tuf-no-std/tests/generated_data/ed25519_metadata/root_with_ed25519.json new file mode 100644 index 0000000..0181de4 --- /dev/null +++ b/tuf-no-std/tests/generated_data/ed25519_metadata/root_with_ed25519.json @@ -0,0 +1,71 @@ +{ + "signatures": [ + { + "keyid": "5822582e7072996c1eef1cec24b61115d364987faa486659fe3d3dce8dae2aba", + "sig": "06fc2b26d10afae02689c96dee96ff40e702734accec6f3e69671dec0a59e0763bd7cb7d5ffa4b9e87441c4c98e798ce97cb462e7075e38ad9bc1d0d0c657309" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": true, + "expires": "2050-01-01T00:00:00Z", + "keys": { + "09d440e3725cec247dcb8703b646a87dd2a4d75343e8095c036c32795eefe3b9": { + "keytype": "ed25519", + "keyval": { + "public": "250f9ae3d1d3d5c419a73cfb4a470c01de1d5d3d61a3825416b5f5d6b88f4a30" + }, + "scheme": "ed25519" + }, + "2be5c21e3614f9f178fb49c4a34d0c18ffac30abd14ced917c60a52c8d8094b7": { + "keytype": "ed25519", + "keyval": { + "public": "0e6738fc1ac6fb4de680b4be99ecbcd99b030f3963f291277eef67bb9bd123e9" + }, + "scheme": "ed25519" + }, + "3458204ed467519c19a5316eb278b5608472a1bbf15850ebfb462d5315e4f86d": { + "keytype": "ed25519", + "keyval": { + "public": "82380623abb9666d4bf274b1a02577469445a972e5650d270101faa5107b19c8" + }, + "scheme": "ed25519" + }, + "5822582e7072996c1eef1cec24b61115d364987faa486659fe3d3dce8dae2aba": { + "keytype": "ed25519", + "keyval": { + "public": "b11d2ff132c033a657318c74c39526476c56de7556c776f11070842dbc4ac14c" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "5822582e7072996c1eef1cec24b61115d364987faa486659fe3d3dce8dae2aba" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "3458204ed467519c19a5316eb278b5608472a1bbf15850ebfb462d5315e4f86d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2be5c21e3614f9f178fb49c4a34d0c18ffac30abd14ced917c60a52c8d8094b7" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "09d440e3725cec247dcb8703b646a87dd2a4d75343e8095c036c32795eefe3b9" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.31", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/generated_data/ed25519_metadata/snapshot_with_ed25519.json b/tuf-no-std/tests/generated_data/ed25519_metadata/snapshot_with_ed25519.json new file mode 100644 index 0000000..ec14913 --- /dev/null +++ b/tuf-no-std/tests/generated_data/ed25519_metadata/snapshot_with_ed25519.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "3458204ed467519c19a5316eb278b5608472a1bbf15850ebfb462d5315e4f86d", + "sig": "bab356be0a82b85b9529aa4625cbd7b8e03b71d1a0fb5d3242f6e8377f102bcf60cc1b8c2a566fd5618c5f5ee3fc07745e84920d26e5514ad455868d7899ae03" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2050-01-01T00:00:00Z", + "meta": { + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.31", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/generated_data/ed25519_metadata/targets_with_ed25519.json b/tuf-no-std/tests/generated_data/ed25519_metadata/targets_with_ed25519.json new file mode 100644 index 0000000..36572b5 --- /dev/null +++ b/tuf-no-std/tests/generated_data/ed25519_metadata/targets_with_ed25519.json @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "2be5c21e3614f9f178fb49c4a34d0c18ffac30abd14ced917c60a52c8d8094b7", + "sig": "9e47f85b3edc79b7215bfee1291da46655deca0b6de99cb3968293218f3329855e57c1523120a50e3a2a8cc50aa9e886f4f74d902d28f43559f294681152f30b" + } + ], + "signed": { + "_type": "targets", + "expires": "2050-01-01T00:00:00Z", + "spec_version": "1.0.31", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/generated_data/ed25519_metadata/timestamp_with_ed25519.json b/tuf-no-std/tests/generated_data/ed25519_metadata/timestamp_with_ed25519.json new file mode 100644 index 0000000..b789090 --- /dev/null +++ b/tuf-no-std/tests/generated_data/ed25519_metadata/timestamp_with_ed25519.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "09d440e3725cec247dcb8703b646a87dd2a4d75343e8095c036c32795eefe3b9", + "sig": "f1b1921a5963485eb5f1cf83f1b44548420bdcced420a367f0c42b63c91950410287f6d062824941085361c3906bb44a365352e2971787a653443ff8df484007" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2050-01-01T00:00:00Z", + "meta": { + "snapshot.json": { + "version": 1 + } + }, + "spec_version": "1.0.31", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/registry.npmjs.org.json b/tuf-no-std/tests/registry.npmjs.org.json new file mode 100644 index 0000000..49d5d5f --- /dev/null +++ b/tuf-no-std/tests/registry.npmjs.org.json @@ -0,0 +1,23 @@ +{ + "signed": { + "_type": "targets", + "spec_version": "1.0", + "version": 1, + "expires": "2023-10-04T13:26:23Z", + "targets": { + "registry.npmjs.org/keys.json": { + "length": 1017, + "hashes": { + "sha256": "7a8ec9678ad824cdccaa7a6dc0961caf8f8df61bc7274189122c123446248426", + "sha512": "881a853ee92d8cf513b07c164fea36b22a7305c256125bdfffdc5c65a4205c4c3fc2b5bcc98964349167ea68d40b8cd02551fcaa870a30d4601ba1caf6f63699" + } + } + } + }, + "signatures": [ + { + "keyid": "a89d235ee2f298d757438c7473b11b0b7b42ff1a45f1dfaac4c014183d6f8c45", + "sig": "304402201acf213d8e3dea0a5f0c1757e4409a02661bcfab3278419307994ead07c36cb4022029986ed2241c630c4e45bf97e043c23220df7612e3718f921fe8a99c7515ad30" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/README.md b/tuf-no-std/tests/repository_data/README.md new file mode 100644 index 0000000..9819e1c --- /dev/null +++ b/tuf-no-std/tests/repository_data/README.md @@ -0,0 +1,48 @@ +# Unit and integration testing + +## Running the tests +The unit and integration tests can be executed by invoking `tox` from any +path under the project directory. + +``` +$ tox +``` + +Or by invoking `aggregate_tests.py` from the +[tests](https://github.com/theupdateframework/python-tuf/tree/develop/tests) +directory. + +``` +$ python3 aggregate_tests.py +``` + +Note: integration tests end in `_integration.py`. + +If you wish to run a particular unit test, navigate to the tests directory and +run that specific unit test. For example: + +``` +$ python3 test_updater.py +``` + +It it also possible to run the test cases of a unit test. For instance: + +``` +$ python3 -m unittest test_updater.TestMultiRepoUpdater.test_get_one_valid_targetinfo +``` + +## Setup +The unit and integration tests operate on static metadata available in the +[repository_data +directory](https://github.com/theupdateframework/python-tuf/tree/develop/tests/repository_data/). +Before running the tests, static metadata is first copied to temporary +directories and modified, as needed, by the tests. + +The test modules typically spawn HTTP(S) servers that serve metadata and target +files for the unit tests. The [map +file](https://github.com/theupdateframework/python-tuf/tree/develop/tests/repository_data) +specifies the location of the test repositories and other properties. For +specific targets and metadata provided by the tests repositories, please +inspect their [respective +metadata](https://github.com/theupdateframework/python-tuf/tree/develop/tests/repository_data/repository). + diff --git a/tuf-no-std/tests/repository_data/client/map.json b/tuf-no-std/tests/repository_data/client/map.json new file mode 100644 index 0000000..d683880 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/map.json @@ -0,0 +1,33 @@ +{ + "mapping": [ + { + "paths": [ + "*1.txt" + ], + "repositories": [ + "test_repository1", + "test_repository2" + ], + "terminating": false, + "threshold": 1 + }, + { + "paths": [ + "*3.txt" + ], + "repositories": [ + "test_repository2" + ], + "terminating": true, + "threshold": 1 + } + ], + "repositories": { + "test_repository1": [ + "http://localhost:30001" + ], + "test_repository2": [ + "http://localhost:30002" + ] + } +} diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/1.root.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/1.root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/1.root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role1.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role1.json new file mode 100644 index 0000000..0ac4687 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role1.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role2", + "paths": [], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file3.txt": { + "hashes": { + "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", + "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" + }, + "length": 28 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role2.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role2.json new file mode 100644 index 0000000..93f378a --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/role2.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "6c32f8cc2c642803a7b3b022ede0cf727e82964c1aa934571ef366bd5050ed02cfe3fdfe5477c08d0cbcc2dd17bb786d37ab1ce2b27e01ad79faf087594e0300" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": {}, + "roles": [] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/root.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/snapshot.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/snapshot.json new file mode 100644 index 0000000..7c8c091 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/snapshot.json @@ -0,0 +1,25 @@ +{ + "signatures": [ + { + "keyid": "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d", + "sig": "085672c70dffe26610e58542ee552843633cfed973abdad94c56138dbf0cd991644f2d3f27e4dda3098e08ab676e7f52627b587947ae69db1012d59a6da18e0c" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "role1.json": { + "version": 1 + }, + "role2.json": { + "version": 1 + }, + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/targets.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/targets.json new file mode 100644 index 0000000..8e21c26 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/targets.json @@ -0,0 +1,61 @@ +{ + "signatures": [ + { + "keyid": "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093", + "sig": "d65f8db0c1a8f0976552b9742bbb393f24a5fa5eaf145c37aee047236c79dd0b83cfbb8b49fa7803689dfe0031dcf22c4d006b593acac07d69093b9b81722c08" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role1", + "paths": [ + "file3.txt" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file1.txt": { + "custom": { + "file_permissions": "0644" + }, + "hashes": { + "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da", + "sha512": "467430a68afae8e9f9c0771ea5d78bf0b3a0d79a2d3d3b40c69fde4dd42c461448aef76fcef4f5284931a1ffd0ac096d138ba3a0d6ca83fa8d7285a47a296f77" + }, + "length": 31 + }, + "file2.txt": { + "hashes": { + "sha256": "452ce8308500d83ef44248d8e6062359211992fd837ea9e370e561efb1a4ca99", + "sha512": "052b49a21e03606b28942db69aa597530fe52d47ee3d748ba65afcd14b857738e36bc1714c4f4adde46c3e683548552fe5c96722e0e0da3acd9050c2524902d8" + }, + "length": 39 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/timestamp.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/timestamp.json new file mode 100644 index 0000000..9a0daf0 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/current/timestamp.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758", + "sig": "de0e16920f87bf5500cc65736488ac17e09788cce808f6a4e85eb9e4e478a312b4c1a2d7723af56f7bfb1df533c67d8c93b6f49d39eabe7fae391a08e1f72f01" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "snapshot.json": { + "hashes": { + "sha256": "8f88e2ba48b412c3843e9bb26e1b6f8fc9e98aceb0fbaa97ba37b4c98717d7ab" + }, + "length": 515, + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/1.root.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/1.root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/1.root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role1.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role1.json new file mode 100644 index 0000000..0ac4687 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role1.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role2", + "paths": [], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file3.txt": { + "hashes": { + "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", + "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" + }, + "length": 28 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role2.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role2.json new file mode 100644 index 0000000..93f378a --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/role2.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "6c32f8cc2c642803a7b3b022ede0cf727e82964c1aa934571ef366bd5050ed02cfe3fdfe5477c08d0cbcc2dd17bb786d37ab1ce2b27e01ad79faf087594e0300" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": {}, + "roles": [] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/root.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/snapshot.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/snapshot.json new file mode 100644 index 0000000..7c8c091 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/snapshot.json @@ -0,0 +1,25 @@ +{ + "signatures": [ + { + "keyid": "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d", + "sig": "085672c70dffe26610e58542ee552843633cfed973abdad94c56138dbf0cd991644f2d3f27e4dda3098e08ab676e7f52627b587947ae69db1012d59a6da18e0c" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "role1.json": { + "version": 1 + }, + "role2.json": { + "version": 1 + }, + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/targets.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/targets.json new file mode 100644 index 0000000..8e21c26 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/targets.json @@ -0,0 +1,61 @@ +{ + "signatures": [ + { + "keyid": "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093", + "sig": "d65f8db0c1a8f0976552b9742bbb393f24a5fa5eaf145c37aee047236c79dd0b83cfbb8b49fa7803689dfe0031dcf22c4d006b593acac07d69093b9b81722c08" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role1", + "paths": [ + "file3.txt" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file1.txt": { + "custom": { + "file_permissions": "0644" + }, + "hashes": { + "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da", + "sha512": "467430a68afae8e9f9c0771ea5d78bf0b3a0d79a2d3d3b40c69fde4dd42c461448aef76fcef4f5284931a1ffd0ac096d138ba3a0d6ca83fa8d7285a47a296f77" + }, + "length": 31 + }, + "file2.txt": { + "hashes": { + "sha256": "452ce8308500d83ef44248d8e6062359211992fd837ea9e370e561efb1a4ca99", + "sha512": "052b49a21e03606b28942db69aa597530fe52d47ee3d748ba65afcd14b857738e36bc1714c4f4adde46c3e683548552fe5c96722e0e0da3acd9050c2524902d8" + }, + "length": 39 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/timestamp.json b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/timestamp.json new file mode 100644 index 0000000..9a0daf0 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository1/metadata/previous/timestamp.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758", + "sig": "de0e16920f87bf5500cc65736488ac17e09788cce808f6a4e85eb9e4e478a312b4c1a2d7723af56f7bfb1df533c67d8c93b6f49d39eabe7fae391a08e1f72f01" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "snapshot.json": { + "hashes": { + "sha256": "8f88e2ba48b412c3843e9bb26e1b6f8fc9e98aceb0fbaa97ba37b4c98717d7ab" + }, + "length": 515, + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/1.root.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/1.root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/1.root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role1.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role1.json new file mode 100644 index 0000000..0ac4687 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role1.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role2", + "paths": [], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file3.txt": { + "hashes": { + "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", + "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" + }, + "length": 28 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role2.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role2.json new file mode 100644 index 0000000..93f378a --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/role2.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "6c32f8cc2c642803a7b3b022ede0cf727e82964c1aa934571ef366bd5050ed02cfe3fdfe5477c08d0cbcc2dd17bb786d37ab1ce2b27e01ad79faf087594e0300" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": {}, + "roles": [] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/root.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/snapshot.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/snapshot.json new file mode 100644 index 0000000..7c8c091 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/snapshot.json @@ -0,0 +1,25 @@ +{ + "signatures": [ + { + "keyid": "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d", + "sig": "085672c70dffe26610e58542ee552843633cfed973abdad94c56138dbf0cd991644f2d3f27e4dda3098e08ab676e7f52627b587947ae69db1012d59a6da18e0c" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "role1.json": { + "version": 1 + }, + "role2.json": { + "version": 1 + }, + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/targets.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/targets.json new file mode 100644 index 0000000..8e21c26 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/targets.json @@ -0,0 +1,61 @@ +{ + "signatures": [ + { + "keyid": "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093", + "sig": "d65f8db0c1a8f0976552b9742bbb393f24a5fa5eaf145c37aee047236c79dd0b83cfbb8b49fa7803689dfe0031dcf22c4d006b593acac07d69093b9b81722c08" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role1", + "paths": [ + "file3.txt" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file1.txt": { + "custom": { + "file_permissions": "0644" + }, + "hashes": { + "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da", + "sha512": "467430a68afae8e9f9c0771ea5d78bf0b3a0d79a2d3d3b40c69fde4dd42c461448aef76fcef4f5284931a1ffd0ac096d138ba3a0d6ca83fa8d7285a47a296f77" + }, + "length": 31 + }, + "file2.txt": { + "hashes": { + "sha256": "452ce8308500d83ef44248d8e6062359211992fd837ea9e370e561efb1a4ca99", + "sha512": "052b49a21e03606b28942db69aa597530fe52d47ee3d748ba65afcd14b857738e36bc1714c4f4adde46c3e683548552fe5c96722e0e0da3acd9050c2524902d8" + }, + "length": 39 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/timestamp.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/timestamp.json new file mode 100644 index 0000000..9a0daf0 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/current/timestamp.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758", + "sig": "de0e16920f87bf5500cc65736488ac17e09788cce808f6a4e85eb9e4e478a312b4c1a2d7723af56f7bfb1df533c67d8c93b6f49d39eabe7fae391a08e1f72f01" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "snapshot.json": { + "hashes": { + "sha256": "8f88e2ba48b412c3843e9bb26e1b6f8fc9e98aceb0fbaa97ba37b4c98717d7ab" + }, + "length": 515, + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/1.root.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/1.root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/1.root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role1.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role1.json new file mode 100644 index 0000000..0ac4687 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role1.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role2", + "paths": [], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file3.txt": { + "hashes": { + "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", + "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" + }, + "length": 28 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role2.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role2.json new file mode 100644 index 0000000..93f378a --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/role2.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "6c32f8cc2c642803a7b3b022ede0cf727e82964c1aa934571ef366bd5050ed02cfe3fdfe5477c08d0cbcc2dd17bb786d37ab1ce2b27e01ad79faf087594e0300" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": {}, + "roles": [] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/root.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/snapshot.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/snapshot.json new file mode 100644 index 0000000..7c8c091 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/snapshot.json @@ -0,0 +1,25 @@ +{ + "signatures": [ + { + "keyid": "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d", + "sig": "085672c70dffe26610e58542ee552843633cfed973abdad94c56138dbf0cd991644f2d3f27e4dda3098e08ab676e7f52627b587947ae69db1012d59a6da18e0c" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "role1.json": { + "version": 1 + }, + "role2.json": { + "version": 1 + }, + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/targets.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/targets.json new file mode 100644 index 0000000..8e21c26 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/targets.json @@ -0,0 +1,61 @@ +{ + "signatures": [ + { + "keyid": "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093", + "sig": "d65f8db0c1a8f0976552b9742bbb393f24a5fa5eaf145c37aee047236c79dd0b83cfbb8b49fa7803689dfe0031dcf22c4d006b593acac07d69093b9b81722c08" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role1", + "paths": [ + "file3.txt" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file1.txt": { + "custom": { + "file_permissions": "0644" + }, + "hashes": { + "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da", + "sha512": "467430a68afae8e9f9c0771ea5d78bf0b3a0d79a2d3d3b40c69fde4dd42c461448aef76fcef4f5284931a1ffd0ac096d138ba3a0d6ca83fa8d7285a47a296f77" + }, + "length": 31 + }, + "file2.txt": { + "hashes": { + "sha256": "452ce8308500d83ef44248d8e6062359211992fd837ea9e370e561efb1a4ca99", + "sha512": "052b49a21e03606b28942db69aa597530fe52d47ee3d748ba65afcd14b857738e36bc1714c4f4adde46c3e683548552fe5c96722e0e0da3acd9050c2524902d8" + }, + "length": 39 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/timestamp.json b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/timestamp.json new file mode 100644 index 0000000..9a0daf0 --- /dev/null +++ b/tuf-no-std/tests/repository_data/client/test_repository2/metadata/previous/timestamp.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758", + "sig": "de0e16920f87bf5500cc65736488ac17e09788cce808f6a4e85eb9e4e478a312b4c1a2d7723af56f7bfb1df533c67d8c93b6f49d39eabe7fae391a08e1f72f01" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "snapshot.json": { + "hashes": { + "sha256": "8f88e2ba48b412c3843e9bb26e1b6f8fc9e98aceb0fbaa97ba37b4c98717d7ab" + }, + "length": 515, + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/fishy_rolenames/1.a.json b/tuf-no-std/tests/repository_data/fishy_rolenames/1.a.json new file mode 100644 index 0000000..a55173a --- /dev/null +++ b/tuf-no-std/tests/repository_data/fishy_rolenames/1.a.json @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "f9b50dd62b5540788b5c5cde0842124b64fa467261bc349dd77de49568eed0ef", + "sig": "a36aa69e0c35d8b5b9578bc656ce5d8a76ea05a2c814f59cc710a11f5e3fe6c7bcbef2bfba4812e3b2936f99e89f10862f6320c901e213f1343e79525474920a" + } + ], + "signed": { + "_type": "targets", + "expires": "2050-10-22T11:21:56Z", + "spec_version": "1.0.19", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1...json b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1...json new file mode 100644 index 0000000..e5ae82e --- /dev/null +++ b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1...json @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "80a5bda93ec130c2fda8ce0c619d7b122b24cc2e0743afedf98a8e368d32019c", + "sig": "8fff438c2347dd7c4fb94c43ec347bcd6b0e79521bd11d95121cb8cc25723efa38565a959a6123da0a2375a2093e53f13a5412df9e51397e06b313837d0d590c" + } + ], + "signed": { + "_type": "targets", + "expires": "2050-10-22T11:21:56Z", + "spec_version": "1.0.19", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.root.json b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.root.json new file mode 100644 index 0000000..69cc04c --- /dev/null +++ b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.root.json @@ -0,0 +1,71 @@ +{ + "signatures": [ + { + "keyid": "72b70899257dc30b596af3a9fe141a924af821aff28ed58d1aea0db9f70a70f7", + "sig": "53ae844137dd04abf9d3ed10380ba46fa2726f328963ffe006aa955804afa3b0d100bc59610c1584234a9598ab4b9af762b533174b8b8d8aaf2be8e413c1b304" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": true, + "expires": "2050-10-22T11:21:56Z", + "keys": { + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "72b70899257dc30b596af3a9fe141a924af821aff28ed58d1aea0db9f70a70f7": { + "keytype": "ed25519", + "keyval": { + "public": "3ba219e69666298bce5d1d653a166346aef807c02e32a846aaefcb5190fddeb4" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "72b70899257dc30b596af3a9fe141a924af821aff28ed58d1aea0db9f70a70f7" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.19", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.targets.json b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.targets.json new file mode 100644 index 0000000..285b3f4 --- /dev/null +++ b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.targets.json @@ -0,0 +1,75 @@ +{ + "signatures": [ + { + "keyid": "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093", + "sig": "b390c5d9d5355b963e94dfa30ce04520c462fd869fad968d01f0a3b185db5895807b14435e725ff376adc793fd21ef8f01890ac722c94e9c05ab3797c4887101" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "426edf0d9fa383688c5b40b7b7d15a7cd11a991f12cc20da87f1b48dd6c036a1": { + "keytype": "ed25519", + "keyval": { + "public": "d38eef769f6dee77b6d898dce548c0ea0f90add0072dc28a20769b6421552ec3" + }, + "scheme": "ed25519" + }, + "80a5bda93ec130c2fda8ce0c619d7b122b24cc2e0743afedf98a8e368d32019c": { + "keytype": "ed25519", + "keyval": { + "public": "bb256c0b6d5226a5a9ae8377c0bf68e958fb668d063971f48638b9bae5251f3b" + }, + "scheme": "ed25519" + }, + "f9b50dd62b5540788b5c5cde0842124b64fa467261bc349dd77de49568eed0ef": { + "keytype": "ed25519", + "keyval": { + "public": "da1b8586dc0cdd5fe0d8d428bde62dc63e06138f58cfc39770c424a4636f59f4" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "f9b50dd62b5540788b5c5cde0842124b64fa467261bc349dd77de49568eed0ef" + ], + "name": "../a", + "paths": [ + "*" + ], + "terminating": false, + "threshold": 1 + }, + { + "keyids": [ + "80a5bda93ec130c2fda8ce0c619d7b122b24cc2e0743afedf98a8e368d32019c" + ], + "name": ".", + "paths": [ + "*" + ], + "terminating": false, + "threshold": 1 + }, + { + "keyids": [ + "426edf0d9fa383688c5b40b7b7d15a7cd11a991f12cc20da87f1b48dd6c036a1" + ], + "name": "\u00f6", + "paths": [ + "*" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2050-10-22T11:21:56Z", + "spec_version": "1.0.19", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git "a/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.\303\266.json" "b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.\303\266.json" new file mode 100644 index 0000000..e6aa802 --- /dev/null +++ "b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/1.\303\266.json" @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "426edf0d9fa383688c5b40b7b7d15a7cd11a991f12cc20da87f1b48dd6c036a1", + "sig": "faada7f8c9a238955d5b27dbd88032a6c9068742cb114a66f97c730235a8033dd1ff0647f4bbc2b49210c33655a3d7755e754e245799683b3f4e00a59f3da006" + } + ], + "signed": { + "_type": "targets", + "expires": "2050-10-22T11:21:56Z", + "spec_version": "1.0.19", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/2.snapshot.json b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/2.snapshot.json new file mode 100644 index 0000000..bf91cab --- /dev/null +++ b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/2.snapshot.json @@ -0,0 +1,28 @@ +{ + "signatures": [ + { + "keyid": "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d", + "sig": "5b00100e9cf1c083f8347371ab840cf60124780305124ed7a53fe31bf43473c90b1d2c802ed2f11f5057ba21e6b7a05118b1907f737d2e29c9692aa3345f9801" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2050-10-22T11:21:56Z", + "meta": { + "../a.json": { + "version": 1 + }, + "..json": { + "version": 1 + }, + "targets.json": { + "version": 1 + }, + "\u00f6.json": { + "version": 1 + } + }, + "spec_version": "1.0.19", + "version": 2 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/timestamp.json b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/timestamp.json new file mode 100644 index 0000000..6bde92c --- /dev/null +++ b/tuf-no-std/tests/repository_data/fishy_rolenames/metadata/timestamp.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758", + "sig": "f7003e848366c7e55f474df2c0c68471c44c68a87c0d3c1aa56f64778c91e9c8f22c3adc4dd9ec0535b6b4dc04783f7fa4ca992bed2445c7395a58acff152f0d" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2050-10-22T11:21:56Z", + "meta": { + "snapshot.json": { + "version": 2 + } + }, + "spec_version": "1.0.19", + "version": 2 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/delegation_key b/tuf-no-std/tests/repository_data/keystore/delegation_key new file mode 100644 index 0000000..461169d --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/delegation_key @@ -0,0 +1 @@ +68593a508472ad3007915379e6b1f3c0@@@@100000@@@@615986af4d1ba89aeadc2f489f89b0e8d46da133a6f75c7b162b8f99f63f86ed@@@@8319255f9856c4f40f9d71bc10e79e5d@@@@1dc7b20f1c668a1f544dc39c7a9fcb3c4a4dd34d1cc8c9d8f779bab026cf0b8e0f46e53bc5ed20bf0e5048b94a5d2ea176e79c12bcc7daa65cd55bf810deebeec5bc903ce9e5316d7dbba88f1a2b51d3f9bc782f8fa9b21dff91609ad0260e21a2039223f816d0fe97ace2e204d0025d327b38d27aa6cd87e85aa8883bfcb6d12f93155d72ffd3c7717a0570cf9811eb6d6a340baa0f27433315d83322c685fec02053ff8c173c4ebf91a258e83402f39546821e3352baa7b246e33b2a573a8ff7b289682407abbcb9184249d4304db68d3bf8e124e94377fd62dde5c4f3b7617d483776345154d047d139b1e559351577da315f54e16153c510159e1908231574bcf49c4f96cafe6530e86a09e9eee47bcff78f2fed2984754c895733938999ff085f9e3532d7174fd76dc09921506dd2137e16ec4926998f5d9df8a8ffb3e6649c71bc32571b2e24357739fa1a56be \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/delegation_key.pub b/tuf-no-std/tests/repository_data/keystore/delegation_key.pub new file mode 100644 index 0000000..d600bff --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/delegation_key.pub @@ -0,0 +1 @@ +{"keyval": {"public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/root_key b/tuf-no-std/tests/repository_data/keystore/root_key new file mode 100644 index 0000000..1b8fb14 --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/root_key @@ -0,0 +1,42 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tuf-no-std/tests/repository_data/keystore/root_key.pub b/tuf-no-std/tests/repository_data/keystore/root_key.pub new file mode 100644 index 0000000..11cc245 --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/root_key.pub @@ -0,0 +1,11 @@ +-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe +PkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i +xmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity +fQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa +ndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc +MdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV +z94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y +R47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA +a82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE= +-----END PUBLIC KEY----- diff --git a/tuf-no-std/tests/repository_data/keystore/root_key2 b/tuf-no-std/tests/repository_data/keystore/root_key2 new file mode 100644 index 0000000..8222946 --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/root_key2 @@ -0,0 +1 @@ +77c02ab5647ee765d5f6c5fc202a5b32@@@@100000@@@@7c73c1100fab52dc8695c1b955d31770ed6e53f1820d9020aeb6541c948573d9@@@@98280307ffa9c5f6ff1fea1a4b79d0ea@@@@f3342882b1cf842e3377ab4205c0ca8fab564cc55fa742f55b364a1ac597e93d8c56a9a6e6bbb6a812556077be44a1066ac6781a6ed34b86beaf3985f846f007dab31c46af562e921f03c1ea8d299f15324ab137aa426ee61d396a7e20191aa71a70b670775b2ad48f25de367fb48881c55e93f468c6e59402907e82985c27c94c715161c85c5c1904353ba33c3d129988029f03a2d7d00720118697baaf73a3c4e72f8e538b4323866fe525ddccfcfc6dd45598545f65cd7ab581f5172bc253416283a66621eb03dbabaf33923bb1963f9f8cbae6fd6a1c86736a8f80c8d1ba3cbc3f53b0123ba9b0bdd44f25b65033b19a50ee978d2687d6a2ee724515a20026d0213ced59cda9bfdf37c82c59e1356795fd603d85996f448a3c9357b32de2042997a1d27353ee3866c0ed5218d633e0b28991119d77d147354c7fa2de8a168d17efdfd5fa9a8e528bd47ede4ff697 \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/root_key2.pub b/tuf-no-std/tests/repository_data/keystore/root_key2.pub new file mode 100644 index 0000000..dd5c43b --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/root_key2.pub @@ -0,0 +1 @@ +{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "3ba219e69666298bce5d1d653a166346aef807c02e32a846aaefcb5190fddeb4"}} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/root_key3 b/tuf-no-std/tests/repository_data/keystore/root_key3 new file mode 100644 index 0000000..89c0b2c --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/root_key3 @@ -0,0 +1 @@ +a3d266f446cb23c0248feed240a8a85a@@@@100000@@@@61ea41c73d4b1d8bd7566a9884a2fdb88c1d4e48550341e532768f98c8f4bd3c@@@@46b15764c50c934fcfc041a5fa207337@@@@d84b8c473d5f42d2bbceca28b0087c2c5908673b2a92eb8f1ca91dacc27c1cfac24c98d06191f6f54633dd428e9ca0987f183e8f34322a104dc38a0f4fefcc168f21e203e3abc5842f132df2dcb61d6b31dc19d0ecb50e898655f81e9b8a9730f2bff4c5ca4b6fc0b572a7e3672b6dc814ed127c964d960a57155c29eccf44824442d3c6761662ed2d8a1c48a3222d0f0cb1a58f543ccd852c247522595d987d95d1bf49dfdffaf33f18085460dac791d81347cc576a83c6ebca2625d26ddd294e74fa67f676a02d533b52fc9702237b2c898469a30753d98b091cd6aa713aa7b0c4c741684674084b27862e64adf4b1e88fa22cfcf6eeae8608dd818a4cba020058fa7271028ea9d9a7302c9e50e82972a82ac2080201c0fb9f2fb1cadfe97d62470414428227add1c40594f5135a8169d0d7d0889cb4a1949b015e65f5dc656204c58c463acc5b7872f4a078d0bc5a09a7795187e360e7b225892601aa9065086b24397f653d20e59a656ec86ef94e64d5baf16080f12a7f2461b92f99dfb5bf2e4dadec91cc72d8eede952449fd586c863734d84f31e036ecc96c55ab7baa9b049c20b8281a7c28f5ca42d9cfad6498f51ee907bfd9dc17e2a1bc9b69145ee82a86a90817394c01770581727889d3ba1791592c7ac2e74753485f1811cc4477078732873185240fc1572927d2fef210066bdf015471bd9d1683e8074b3fb6957246589dc62dea4843a17a7c734ae45ae20d31f0083a32d3310fae459fe3fbf7c763e5e4ead4acd9b0233e45237f4465576e85ff707fe316488f329d5bc73596b104cc28b926d6b1f5a3d26a0a6ec534a3cbc54cab97f5cea51f17b8d7f1cc6c9977275c34ee4942dd3e22a19ae1e4252199226cc4fd60 \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/root_key3.pub b/tuf-no-std/tests/repository_data/keystore/root_key3.pub new file mode 100644 index 0000000..ee5d487 --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/root_key3.pub @@ -0,0 +1 @@ +{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4huWFUZelzzZk2xLwnLqyc2q7cfI\nIqgg3qOWSddQ3Q/GBXCzgg7zqNqS+xSt+D3gy3mMBbkeo+6OVm8/W9BrqQ=="}} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/snapshot_key b/tuf-no-std/tests/repository_data/keystore/snapshot_key new file mode 100644 index 0000000..08c954f --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/snapshot_key @@ -0,0 +1 @@ +a87b80b8a0d39b919b9638181e7b274e@@@@100000@@@@132edd670981aaf1980673966266174d944d735eb5b0b7ec83ed97da5c212249@@@@bd08ae9898ac5f81fc14e418e9790f9b@@@@399250c9aad40035e0acff48db59697bc3cf33d55b52aa272246addeaaf318d931d3a72964f0c84eccf5b89279b8233685330ad884f7b39bf369553133b985f9396bd5e24cb8e343643923022565a645e188a1165e427aedc389cca821d6a93cb2d8d16cea8ffeb56469bcb9f2f66e03d581a2ea37da271980dd02b84717fe475e13a305b4ae714c11c94f6711c744bb291a146d7419474584bad4be152d0299273c1fad6cd95232a4bf07f39c16da7f4d13201a88fad822cb328008e8a2762baf974b5d5080451751fb8ef53a01ca734157be78b3eb13c6270e4e98b138c78388360e7f558389871b7a32b4d5572626b3112264a0b56dbbb1138c9765872a71dd4e7d31006c2e690f5ede608ce633ad94ebb7d1ddec1a7eac2168fc5d36efe590c4c2059c6f3bcf75ab63474eede3ce4fdc93c6564058b14a0fa9bf3cb6d58c53315b406409ee4aeb18abe072734df0 \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/snapshot_key.pub b/tuf-no-std/tests/repository_data/keystore/snapshot_key.pub new file mode 100644 index 0000000..d08bb84 --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/snapshot_key.pub @@ -0,0 +1 @@ +{"keyval": {"public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/targets_key b/tuf-no-std/tests/repository_data/keystore/targets_key new file mode 100644 index 0000000..c3883ec --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/targets_key @@ -0,0 +1 @@ +a5a903322888df0bf8275b215f2044fe@@@@100000@@@@5f6b803652cb6d5bce4e07b1482597adb96d06c2efa3393abdcc0425f70be692@@@@0664811967e2f413927ce51a7f43a80e@@@@cf1dccd034400195c667c064198ef25555f3f94bf9cf77fbe300246618e557ad0efa775ef90bd46c842696c45d14033199860b2214c3641e87889a41171f8a2c763d004681b66b462ff34599e8d9da87f5642d2a015b75d3f601d198e0467fa4bc28f65c76260585e0cce71281f67a8053116f0f06883155f602811071b56bf75bf54daae5968b0a31cf829510f3c52c0eeb8f1c6bb8b8cb0c3edb4c6c2dd9d13bee00c5d63c3f98e0904eebb609864f4ab4fcc2c17bba8fd36aa06bc96bc1922eb10557051a674acf2cb01ff3efb7d55411df6915bbc49a095ff4472dc441e2765244f801d0df07b754c952d039f39b4530930a14be42cb2041f22eeb306b12f12158fcd2beb033db1be21f5a6ab72335cf16dfbd19cbf39c00b0a571d2b0e25df032be53a49a7a70ecebebb441d327c638cf31804381afaf809cd1c75f9070e83240fbaaa87bea0799404ece788862 \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/targets_key.pub b/tuf-no-std/tests/repository_data/keystore/targets_key.pub new file mode 100644 index 0000000..e859eb2 --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/targets_key.pub @@ -0,0 +1 @@ +{"keyval": {"public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/timestamp_key b/tuf-no-std/tests/repository_data/keystore/timestamp_key new file mode 100644 index 0000000..ca82579 --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/timestamp_key @@ -0,0 +1 @@ +677a42cd6c1df08d0c6156ae356c2875@@@@100000@@@@3850dbcf2973b80044912d630f05039df64775b63d1cf43e750d3cd8a457c64f@@@@bf01961c386d9fefb4b29db7f6ef0c7f@@@@96d37abafb902f821134d2034855d23b78c82e5b768b092fcf0d3b6b28a74734877a5014b26e5fed289d24f7cf6b393445c3231554c5b6d9711192cf9bd2fb7490497d7d76c619a0cfc70abae026b5068fb66db0138b04f890917daad66ca1f7baabdcbb5282e46a2f1c6ff2e8c241ff16ef31e918ca1387a15bc2ceadb2f75ce68fcff08186b5b901a499efe1f674319b503ff8b6fc004b71d0ecb94253f38c58349ab749e72f492e541e7504d25a0bfe791f53eb95c4524431b0f952fc3d7c7204a2a4aab44d33fe09cb36b337339e2a004bf15dfd925b63930905972749441a0c6e50ec9b1748a4cfbacf10b402ebd9c0074fcb38d236fd3146f60232862b0501e8e6caa9f81c223de03ba7b25a1d4bc2d031901dc445f25ce302d2189b8b8de443bc6f562f941b55595655193ab6b84c1ec2302ca056c70e8efb1cad909c50e82e0b7da9ad64202d149e4e837409 \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/keystore/timestamp_key.pub b/tuf-no-std/tests/repository_data/keystore/timestamp_key.pub new file mode 100644 index 0000000..69ba7de --- /dev/null +++ b/tuf-no-std/tests/repository_data/keystore/timestamp_key.pub @@ -0,0 +1 @@ +{"keyval": {"public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/map.json b/tuf-no-std/tests/repository_data/map.json new file mode 100644 index 0000000..c2e0fb8 --- /dev/null +++ b/tuf-no-std/tests/repository_data/map.json @@ -0,0 +1,29 @@ +{ + "mapping": [ + { + "paths": [ + "*1.txt" + ], + "repositories": [ + "test_repository1", + "test_repository2" + ], + "terminating": false, + "threshold": 1 + }, + { + "paths": [ + "*3.txt" + ], + "repositories": [ + "test_repository2" + ], + "terminating": true, + "threshold": 1 + } + ], + "repositories": { + "test_repository1": [], + "test_repository2": [] + } +} diff --git a/tuf-no-std/tests/repository_data/project/targets/file1.txt b/tuf-no-std/tests/repository_data/project/targets/file1.txt new file mode 100644 index 0000000..7bf3499 --- /dev/null +++ b/tuf-no-std/tests/repository_data/project/targets/file1.txt @@ -0,0 +1 @@ +This is an example target file. \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/project/targets/file2.txt b/tuf-no-std/tests/repository_data/project/targets/file2.txt new file mode 100644 index 0000000..606f18e --- /dev/null +++ b/tuf-no-std/tests/repository_data/project/targets/file2.txt @@ -0,0 +1 @@ +This is an another example target file. \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/project/targets/file3.txt b/tuf-no-std/tests/repository_data/project/targets/file3.txt new file mode 100644 index 0000000..6046460 --- /dev/null +++ b/tuf-no-std/tests/repository_data/project/targets/file3.txt @@ -0,0 +1 @@ +This is role1's target file. \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/project/test-flat/project.cfg b/tuf-no-std/tests/repository_data/project/test-flat/project.cfg new file mode 100644 index 0000000..1564431 --- /dev/null +++ b/tuf-no-std/tests/repository_data/project/test-flat/project.cfg @@ -0,0 +1 @@ +{"project_name": "test-flat", "targets_location": "/Users/vlad/projects/vladforks/tuf/tests/repository_data/project/targets", "prefix": "prefix", "metadata_location": "test-flat", "threshold": 1, "public_keys": {"4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": {"keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----"}, "keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyid_hash_algorithms": ["sha256", "sha512"]}}, "layout_type": "flat"} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/project/test-flat/role1.json b/tuf-no-std/tests/repository_data/project/test-flat/role1.json new file mode 100644 index 0000000..93f378a --- /dev/null +++ b/tuf-no-std/tests/repository_data/project/test-flat/role1.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "6c32f8cc2c642803a7b3b022ede0cf727e82964c1aa934571ef366bd5050ed02cfe3fdfe5477c08d0cbcc2dd17bb786d37ab1ce2b27e01ad79faf087594e0300" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": {}, + "roles": [] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/project/test-flat/test-flat.json b/tuf-no-std/tests/repository_data/project/test-flat/test-flat.json new file mode 100644 index 0000000..15f3a49 --- /dev/null +++ b/tuf-no-std/tests/repository_data/project/test-flat/test-flat.json @@ -0,0 +1,58 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "89d042b364a853d3a9f7caf16005d88102a21a63b4841bf67c81c8cda4de7aff6fe2c877e94c0693961f423230da47055fa88f73ecc5b6f47515af61e4e85cbdb19af87a681dc202c8bdcdfdb0d43920198dde97f0b27b404c93308083225f311d196a7e8bbcf921a68580e760fb4616828438dd1236ae0639eda4c1f98e4074e6b5eb79043de9d3dc30e29f993dddf808e533947055d1a7b4b4d2344ed5c327568ac9065fdb7649c753aa4a37751284da57b236a9d171f00d0e7f93fc56bacd966d025bec3c21f769f94fc21c70e2b9c8d4b2cddb2bcae30b9626edd9a4c954a1c5a9e49b6b079bd15b44413987b98a69f92cf950a18049ba13d2dd1c338e1d2cd0625b659ac71df8bfbd49d7be31cdabddef7a4ecf83b674016028f519ca0d421150c9fe71abf7130644b494bf88861c365787e345b95a019e0cb6d403ad8d628fbc3b9f397824104bcf00e20fb7135895a7265b516f9b1ccd18c9b82a327b211297143d2ab16e8ac3ce98ab24c0b84d91c6e3b3d451858da1cdfbbafc4dd7" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role1", + "paths": [ + "project/targets/file3.txt" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "prefix/file1.txt": { + "hashes": { + "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da", + "sha512": "467430a68afae8e9f9c0771ea5d78bf0b3a0d79a2d3d3b40c69fde4dd42c461448aef76fcef4f5284931a1ffd0ac096d138ba3a0d6ca83fa8d7285a47a296f77" + }, + "length": 31 + }, + "prefix/file2.txt": { + "hashes": { + "sha256": "452ce8308500d83ef44248d8e6062359211992fd837ea9e370e561efb1a4ca99", + "sha512": "052b49a21e03606b28942db69aa597530fe52d47ee3d748ba65afcd14b857738e36bc1714c4f4adde46c3e683548552fe5c96722e0e0da3acd9050c2524902d8" + }, + "length": 39 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata.staged/1.root.json b/tuf-no-std/tests/repository_data/repository/metadata.staged/1.root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata.staged/1.root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata.staged/role1.json b/tuf-no-std/tests/repository_data/repository/metadata.staged/role1.json new file mode 100644 index 0000000..0ac4687 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata.staged/role1.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role2", + "paths": [], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file3.txt": { + "hashes": { + "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", + "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" + }, + "length": 28 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata.staged/role2.json b/tuf-no-std/tests/repository_data/repository/metadata.staged/role2.json new file mode 100644 index 0000000..93f378a --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata.staged/role2.json @@ -0,0 +1,19 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "6c32f8cc2c642803a7b3b022ede0cf727e82964c1aa934571ef366bd5050ed02cfe3fdfe5477c08d0cbcc2dd17bb786d37ab1ce2b27e01ad79faf087594e0300" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": {}, + "roles": [] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata.staged/root.json b/tuf-no-std/tests/repository_data/repository/metadata.staged/root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata.staged/root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata.staged/snapshot.json b/tuf-no-std/tests/repository_data/repository/metadata.staged/snapshot.json new file mode 100644 index 0000000..7c8c091 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata.staged/snapshot.json @@ -0,0 +1,25 @@ +{ + "signatures": [ + { + "keyid": "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d", + "sig": "085672c70dffe26610e58542ee552843633cfed973abdad94c56138dbf0cd991644f2d3f27e4dda3098e08ab676e7f52627b587947ae69db1012d59a6da18e0c" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "role1.json": { + "version": 1 + }, + "role2.json": { + "version": 1 + }, + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata.staged/targets.json b/tuf-no-std/tests/repository_data/repository/metadata.staged/targets.json new file mode 100644 index 0000000..8e21c26 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata.staged/targets.json @@ -0,0 +1,61 @@ +{ + "signatures": [ + { + "keyid": "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093", + "sig": "d65f8db0c1a8f0976552b9742bbb393f24a5fa5eaf145c37aee047236c79dd0b83cfbb8b49fa7803689dfe0031dcf22c4d006b593acac07d69093b9b81722c08" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role1", + "paths": [ + "file3.txt" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file1.txt": { + "custom": { + "file_permissions": "0644" + }, + "hashes": { + "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da", + "sha512": "467430a68afae8e9f9c0771ea5d78bf0b3a0d79a2d3d3b40c69fde4dd42c461448aef76fcef4f5284931a1ffd0ac096d138ba3a0d6ca83fa8d7285a47a296f77" + }, + "length": 31 + }, + "file2.txt": { + "hashes": { + "sha256": "452ce8308500d83ef44248d8e6062359211992fd837ea9e370e561efb1a4ca99", + "sha512": "052b49a21e03606b28942db69aa597530fe52d47ee3d748ba65afcd14b857738e36bc1714c4f4adde46c3e683548552fe5c96722e0e0da3acd9050c2524902d8" + }, + "length": 39 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata.staged/timestamp.json b/tuf-no-std/tests/repository_data/repository/metadata.staged/timestamp.json new file mode 100644 index 0000000..9a0daf0 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata.staged/timestamp.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758", + "sig": "de0e16920f87bf5500cc65736488ac17e09788cce808f6a4e85eb9e4e478a312b4c1a2d7723af56f7bfb1df533c67d8c93b6f49d39eabe7fae391a08e1f72f01" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "snapshot.json": { + "hashes": { + "sha256": "8f88e2ba48b412c3843e9bb26e1b6f8fc9e98aceb0fbaa97ba37b4c98717d7ab" + }, + "length": 515, + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata/1.root.json b/tuf-no-std/tests/repository_data/repository/metadata/1.root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata/1.root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata/role1.json b/tuf-no-std/tests/repository_data/repository/metadata/role1.json new file mode 100644 index 0000000..0ac4687 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata/role1.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role2", + "paths": [], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file3.txt": { + "hashes": { + "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", + "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" + }, + "length": 28 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata/role2.json b/tuf-no-std/tests/repository_data/repository/metadata/role2.json new file mode 100644 index 0000000..9c49e16 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata/role2.json @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", + "sig": "75b196a224fd200e46e738b1216b3316c5384f61083872f8d14b8b0a378b2344e64b1a6f1a89a711206a66a0b199d65ac0e30fe15ddbc4de89fa8ff645f99403" + } + ], + "signed": { + "_type": "targets", + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata/root.json b/tuf-no-std/tests/repository_data/repository/metadata/root.json new file mode 100644 index 0000000..214d8db --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata/root.json @@ -0,0 +1,87 @@ +{ + "signatures": [ + { + "keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb", + "sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "rsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----" + }, + "scheme": "rsassa-pss-sha256" + }, + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd" + }, + "scheme": "ed25519" + }, + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815" + }, + "scheme": "ed25519" + }, + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4" + }, + "scheme": "ed25519" + } + }, + "roles": { + "root": { + "keyids": [ + "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758" + ], + "threshold": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata/snapshot.json b/tuf-no-std/tests/repository_data/repository/metadata/snapshot.json new file mode 100644 index 0000000..7c8c091 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata/snapshot.json @@ -0,0 +1,25 @@ +{ + "signatures": [ + { + "keyid": "59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d", + "sig": "085672c70dffe26610e58542ee552843633cfed973abdad94c56138dbf0cd991644f2d3f27e4dda3098e08ab676e7f52627b587947ae69db1012d59a6da18e0c" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "role1.json": { + "version": 1 + }, + "role2.json": { + "version": 1 + }, + "targets.json": { + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata/targets.json b/tuf-no-std/tests/repository_data/repository/metadata/targets.json new file mode 100644 index 0000000..8e21c26 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata/targets.json @@ -0,0 +1,61 @@ +{ + "signatures": [ + { + "keyid": "65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093", + "sig": "d65f8db0c1a8f0976552b9742bbb393f24a5fa5eaf145c37aee047236c79dd0b83cfbb8b49fa7803689dfe0031dcf22c4d006b593acac07d69093b9b81722c08" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ed25519", + "keyval": { + "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" + }, + "scheme": "ed25519" + } + }, + "roles": [ + { + "keyids": [ + "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" + ], + "name": "role1", + "paths": [ + "file3.txt" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2030-01-01T00:00:00Z", + "spec_version": "1.0.0", + "targets": { + "file1.txt": { + "custom": { + "file_permissions": "0644" + }, + "hashes": { + "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da", + "sha512": "467430a68afae8e9f9c0771ea5d78bf0b3a0d79a2d3d3b40c69fde4dd42c461448aef76fcef4f5284931a1ffd0ac096d138ba3a0d6ca83fa8d7285a47a296f77" + }, + "length": 31 + }, + "file2.txt": { + "hashes": { + "sha256": "452ce8308500d83ef44248d8e6062359211992fd837ea9e370e561efb1a4ca99", + "sha512": "052b49a21e03606b28942db69aa597530fe52d47ee3d748ba65afcd14b857738e36bc1714c4f4adde46c3e683548552fe5c96722e0e0da3acd9050c2524902d8" + }, + "length": 39 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/metadata/timestamp.json b/tuf-no-std/tests/repository_data/repository/metadata/timestamp.json new file mode 100644 index 0000000..9a0daf0 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/metadata/timestamp.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758", + "sig": "de0e16920f87bf5500cc65736488ac17e09788cce808f6a4e85eb9e4e478a312b4c1a2d7723af56f7bfb1df533c67d8c93b6f49d39eabe7fae391a08e1f72f01" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "snapshot.json": { + "hashes": { + "sha256": "8f88e2ba48b412c3843e9bb26e1b6f8fc9e98aceb0fbaa97ba37b4c98717d7ab" + }, + "length": 515, + "version": 1 + } + }, + "spec_version": "1.0.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/targets/file1.txt b/tuf-no-std/tests/repository_data/repository/targets/file1.txt new file mode 100644 index 0000000..7bf3499 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/targets/file1.txt @@ -0,0 +1 @@ +This is an example target file. \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/targets/file2.txt b/tuf-no-std/tests/repository_data/repository/targets/file2.txt new file mode 100644 index 0000000..606f18e --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/targets/file2.txt @@ -0,0 +1 @@ +This is an another example target file. \ No newline at end of file diff --git a/tuf-no-std/tests/repository_data/repository/targets/file3.txt b/tuf-no-std/tests/repository_data/repository/targets/file3.txt new file mode 100644 index 0000000..6046460 --- /dev/null +++ b/tuf-no-std/tests/repository_data/repository/targets/file3.txt @@ -0,0 +1 @@ +This is role1's target file. \ No newline at end of file diff --git a/tuf-no-std/tests/root.json b/tuf-no-std/tests/root.json new file mode 100755 index 0000000..aaefda3 --- /dev/null +++ b/tuf-no-std/tests/root.json @@ -0,0 +1,71 @@ +{ + "signatures": [ + { + "keyid": "cb3fbd83df4ba2471a736b065650878280964a98843ec13b457a99b2a21cc3b4", + "sig": "a312b9c3cb4a1b693e8ebac5ee1ca9cc01f2661c14391917dcb111517f72370809f32c890c6b801e30158ac4efe0d4d87317223077784c7a378834249d048306" + } + ], + "signed": { + "_type": "root", + "spec_version": "1.0.0", + "consistent_snapshot": false, + "expires": "2030-01-01T00:00:00Z", + "keys": { + "135c2f50e57ff11e744d234a62cebad8c38daf399604a7655661cc9199c69164": { + "keytype": "ed25519", + "scheme": "ed25519", + "keyval": { + "public": "68ead6e54a43f8f36f9717b10669d1ef0ebb38cee6b05317669341309f1069cb" + } + }, + "1bf1c6e3cdd3d3a8420b19199e27511999850f4b376c4547b2f32fba7e80fca3": { + "keytype": "ed25519", + "scheme": "ed25519", + "keyval": { + "public": "72378e5bc588793e58f81c8533da64a2e8f1565c1fcc7f253496394ffc52542c" + } + }, + "66676daa73bdfb4804b56070c8927ae491e2a6c2314f05b854dea94de8ff6bfc": { + "keytype": "ed25519", + "scheme": "ed25519", + "keyval": { + "public": "01c61f8dc7d77fcef973f4267927541e355e8ceda757e2c402818dad850f856e" + } + }, + "cb3fbd83df4ba2471a736b065650878280964a98843ec13b457a99b2a21cc3b4": { + "keytype": "ed25519", + "scheme": "ed25519", + "keyval": { + "public": "66dd78c5c2a78abc6fc6b267ff1a8017ba0e8bfc853dd97af351949bba021275" + } + } + }, + "roles": { + "root": { + "keyids": [ + "cb3fbd83df4ba2471a736b065650878280964a98843ec13b457a99b2a21cc3b4" + ], + "threshold": 1 + }, + "snapshot": { + "keyids": [ + "66676daa73bdfb4804b56070c8927ae491e2a6c2314f05b854dea94de8ff6bfc" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "135c2f50e57ff11e744d234a62cebad8c38daf399604a7655661cc9199c69164" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "1bf1c6e3cdd3d3a8420b19199e27511999850f4b376c4547b2f32fba7e80fca3" + ], + "threshold": 1 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/rsa-root.json b/tuf-no-std/tests/rsa-root.json new file mode 100644 index 0000000..24ea2d7 --- /dev/null +++ b/tuf-no-std/tests/rsa-root.json @@ -0,0 +1,40 @@ +{ + "signed": { + "_type": "root", + "spec_version": "1.0", + "consistent_snapshot": true, + "version": 1, + "expires": "3000-03-30T03:30:30Z", + "keys": { + "8ec3a843a0f9328c863cac4046ab1cacbbc67888476ac7acf73d9bcd9a223ada": { + "keytype": "rsa", + "scheme": "rsassa-pss-sha256", + "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnL6u6Q9Q6pg1G5020a83\nGlH/aFUO0PQ5leIpwWL8kWgpaWuUG7oRlOUG2/4cwN5FCvJJGXqU5AtSKq2fZ42J\n5XR9QMip4Pg0Q6mE8XCvAXAoMnkWSchdzgT2GoEntaOeRRTCUGb/DsVoxsVXjV6m\nFaRMx7nh8ggshMWgTYgTUDK+CSIBCcBWapCFq1BrM60XZmGTqeAuHSHaUUuF9G3b\ngOflH5L9IpQkaHWbJtGvyKLr53mhWO2r8BPR3+CtNZojAnkwmu4lA94k8C7TLMdc\nutzU4OzODe9UPERc33lRv8DBgsH3F077ZQwv/ikZXWSlACTDWZwenncCEwqdeDd4\n+q2AHyqxRN7bUAh57mUN+kFd3SS/4T44sfBrJw6N4JV/mE+/YfRLWtpIKIsXnBCb\nrC+dt96Vqz6g6eVVvqPwhOCSKcYsmp/iS6qwVn0Dq2SCrGG1FTmBjeA9ZkcjZhUG\nQEMyMNhoS+U2Nx5oIEIq2kREpuu+KsBSTUaOgR07WNUxAgMBAAE=\n-----END PUBLIC KEY-----\n"} + } + }, + "roles": { + "root": { + "keyids": ["8ec3a843a0f9328c863cac4046ab1cacbbc67888476ac7acf73d9bcd9a223ada"], + "threshold": 1 + }, + "snapshot": { + "keyids": ["8ec3a843a0f9328c863cac4046ab1cacbbc67888476ac7acf73d9bcd9a223ada"], + "threshold": 1 + }, + "targets": { + "keyids": ["8ec3a843a0f9328c863cac4046ab1cacbbc67888476ac7acf73d9bcd9a223ada"], + "threshold": 1 + }, + "timestamp": { + "keyids": ["8ec3a843a0f9328c863cac4046ab1cacbbc67888476ac7acf73d9bcd9a223ada"], + "threshold": 1 + } + } + }, + "signatures": [ + { + "keyid": "8ec3a843a0f9328c863cac4046ab1cacbbc67888476ac7acf73d9bcd9a223ada", + "sig": "00988a64c9513713ca39197738f5fc8d5babb3a070e065bb08fa09bd3ab6e0f8f4e822a3de2fd54ecd799c22eb380d3e47ba34052112509ed1e717e91303d47d8b211c09424b5eacf675cff98c53d1aa24370a7218d526119156ec391fe6097649b3b6b5753431a3f9f9d46ee5a3b9f053ab0aa1fabecf78d305ce86f8634bd921e3ba728c00572307a278cc526ecf5c17a1b804149fef6de6e3a2d58d7fbfab9dfc59abe8b9cf02be34325401296e9e0c4eb4375aab102fe208984e5fdddd1f878eb1e1623eb6463cd9f96d114967ff311d2bbf87b6de49594c1e66faf7e07844d2e0dfab33e4f946d3b862218c6404a385731803f5d5dcde1d9a8489f81e7637edf2cb6c9c98676a8424acbf88946d73e4676b378b058840130764210663ef531b26db19b952f5f0177444c2b2a0198684fe5d911f4813eb40f78a7e234e663ebfa9f75fee6d32bec7f9fb97911d1f0919c3837207b73af97fde19fcc62c34ad3062e1ffe3b52b857bceb4e78f79ca5bc780fa17f41c66730d4c138593a581" + } + ] +} diff --git a/tuf-no-std/tests/sigstore-root.json b/tuf-no-std/tests/sigstore-root.json new file mode 100755 index 0000000..7abd5fd --- /dev/null +++ b/tuf-no-std/tests/sigstore-root.json @@ -0,0 +1,156 @@ +{ + "signed": { + "_type": "root", + "spec_version": "1.0", + "version": 5, + "expires": "2023-04-18T18:13:43Z", + "keys": { + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" + } + }, + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" + } + }, + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" + } + }, + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" + } + }, + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" + } + }, + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" + } + }, + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" + } + } + }, + "roles": { + "root": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" + ], + "threshold": 1 + } + }, + "consistent_snapshot": true + }, + "signatures": [ + { + "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", + "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" + }, + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" + }, + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" + }, + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" + }, + { + "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90", + "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/snapshot.json b/tuf-no-std/tests/snapshot.json new file mode 100644 index 0000000..2c172c9 --- /dev/null +++ b/tuf-no-std/tests/snapshot.json @@ -0,0 +1,64 @@ +{ + "signed": { + "_type": "snapshot", + "spec_version": "1.0", + "version": 105, + "expires": "2023-10-10T16:08:34Z", + "meta": { + "registry.npmjs.org.json": { + "length": 713, + "hashes": { + "sha256": "235bb462df505d1dd1c2fbb12ffa1ac65adffdba13d2c9cf01924372f0d182cd", + "sha512": "9e7fa5cd8d0ef677f77e2ec9379739bf83125a8aa97761d431d2305935769ba7aae2663e81a84ddbad9328ed0412f684f5e6fb27b9c9634331b82ab21deab805" + }, + "version": 1 + }, + "rekor.json": { + "length": 797, + "hashes": { + "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b", + "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960" + }, + "version": 3 + }, + "revocation.json": { + "length": 800, + "hashes": { + "sha256": "6f60848ba8fb0955a02abfd1232fb3845dc9ee9f418bf03521a7ddb48217e040", + "sha512": "a965dddd0d0edef6c59e84cf02ecf5a53299f633fd339b2b61814a4219ab4df672a6390f265b8b29e1c8cea9368ea3440df013790759d50231a30df1c1f02551" + }, + "version": 2 + }, + "root.json": { + "length": 5297, + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "version": 2 + }, + "staging.json": { + "length": 401, + "hashes": { + "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149", + "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281" + }, + "version": 2 + }, + "targets.json": { + "length": 5252, + "hashes": { + "sha256": "62e5983ffb9fd792b70a349ec9f7f59d16009914236a0036acef8ebdf8cff445", + "sha512": "b7ef79bb5b0b6619cc0c9696069f2332c702b36d7f86dcf52ccae53c4d7b5d6217b4287137ca38e9bc10dbc2d57a7e3a393c9a8fbf0e4fb30b766f0dd953db27" + }, + "version": 7 + } + } + }, + "signatures": [ + { + "keyid": "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b", + "sig": "3046022100f8a2f2f82bd6fd1d96378574ad65a59f742adf1361753e172786326f5b68f1b2022100eac1a0943901a2fafbff8383e0ef2dcf96a9f0a687bebf7436437f039a63ecd3" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tests/snapshots.json b/tuf-no-std/tests/snapshots.json new file mode 100755 index 0000000..8f6d1e8 --- /dev/null +++ b/tuf-no-std/tests/snapshots.json @@ -0,0 +1,32 @@ +{ + "signatures": [ + { + "keyid": "66676daa73bdfb4804b56070c8927ae491e2a6c2314f05b854dea94de8ff6bfc", + "sig": "f7f03b13e3f4a78a23561419fc0dd741a637e49ee671251be9f8f3fceedfc112e44ee3aaff2278fad9164ab039118d4dc53f22f94900dae9a147aa4d35dcfc0f" + } + ], + "signed": { + "_type": "snapshot", + "spec_version": "1.0.0", + "expires": "2030-01-01T00:00:00Z", + "meta": { + "targets.json": { + "version": 1 + }, + "project1.json": { + "version": 1, + "hashes": { + "sha256": "f592d072e1193688a686267e8e10d7257b4ebfcf28133350dae88362d82a0c8a" + } + }, + "project2.json": { + "version": 1, + "length": 604, + "hashes": { + "sha256": "1f812e378264c3085bb69ec5f6663ed21e5882bbece3c3f8a0e8479f205ffb91" + } + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/targets.json b/tuf-no-std/tests/targets.json new file mode 100755 index 0000000..c54adb7 --- /dev/null +++ b/tuf-no-std/tests/targets.json @@ -0,0 +1,160 @@ +{ + "signatures": [ + { + "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", + "sig": "3045022100eeb820ad0b0aabb0f6bf68159d7a9cbd24d1c4633f14317f9944fcb3bb2e1ff802207f92fb20b84df6bcf0c3c2f39fa089238bbee9ff12490c555b694421a45e8d5d" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "304502202984e536e9442c9f5a14700195b481fb0b3255e4572734700c5ff527b1135f88022100be2f4947b34a02e391417c96ffab8643476164fc44fa258bfe0133ab49858f29" + }, + { + "keyid": "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", + "sig": "3045022100d757e9268801bb1eb4f883abd570dae52a3201c622b4e7e5a30a8b42f3e2f54b02206663b846a8d13d95497d651334dbb754587a14c274fadd91ca796733bc467b95" + }, + { + "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", + "sig": "3045022054d7fb7d70a80f8f4021f090307ada7a196a32f4794954baf405475b38e68d45022100d85fde60eecb2003e4eb329e1b68f22d33dbe11a2293155dad88209250b25b2c" + } + ], + "signed": { + "_type": "targets", + "spec_version": "1.0", + "version": 7, + "expires": "2023-10-04T13:26:23Z", + "targets": { + "artifact.pub": { + "length": 177, + "hashes": { + "sha256": "59ebf97a9850aecec4bc39c1f5c1dc46e6490a6b5fd2a6cacdcac0c3a6fc4cbf", + "sha512": "308fd1d1d95d7f80aa33b837795251cc3e886792982275e062409e13e4e236ffc34d676682aa96fdc751414de99c864bf132dde71581fa651c6343905e3bf988" + }, + "custom": { + "sigstore": { + "status": "Active", + "usage": "Unknown" + } + } + }, + "ctfe.pub": { + "length": 177, + "hashes": { + "sha256": "7fcb94a5d0ed541260473b990b99a6c39864c1fb16f3f3e594a5a3cebbfe138a", + "sha512": "4b20747d1afe2544238ad38cc0cc3010921b177d60ac743767e0ef675b915489bd01a36606c0ff83c06448622d7160f0d866c83d20f0c0f44653dcc3f9aa0bd4" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/test", + "usage": "CTFE" + } + } + }, + "ctfe_2022.pub": { + "length": 178, + "hashes": { + "sha256": "270488a309d22e804eeb245493e87c667658d749006b9fee9cc614572d4fbbdc", + "sha512": "e83fa4f427b24ee7728637fad1b4aa45ebde2ba02751fa860694b1bb16059a490328f9985e51cc70e4d237545315a1bc866dc4fdeef2f6248d99cc7a6077bf85" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://ctfe.sigstore.dev/2022", + "usage": "CTFE" + } + } + }, + "fulcio.crt.pem": { + "length": 744, + "hashes": { + "sha256": "f360c53b2e13495a628b9b8096455badcb6d375b185c4816d95a5d746ff29908", + "sha512": "0713252a7fd17f7f3ab12f88a64accf2eb14b8ad40ca711d7fe8b4ecba3b24db9e9dffadb997b196d3867b8f9ff217faf930d80e4dab4e235c7fc3f07be69224" + }, + "custom": { + "sigstore": { + "status": "Expired", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_intermediate_v1.crt.pem": { + "length": 789, + "hashes": { + "sha256": "f8cbecf186db7714624a5f4e99da31a917cbef70a94dd6921f5c3ca969dfe30a", + "sha512": "0f99f47dbc26c5f1e3cba0bfd9af4245a26e5cb735d6ef005792ec7e603f66fdb897de985973a6e50940ca7eff5e1849719e967b5ad2dac74a29115a41cf6f21" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "fulcio_v1.crt.pem": { + "length": 740, + "hashes": { + "sha256": "f989aa23def87c549404eadba767768d2a3c8d6d30a8b793f9f518a8eafd2cf5", + "sha512": "f2e33a6dc208cee1f51d33bbea675ab0f0ced269617497985f9a0680689ee7073e4b6f8fef64c91bda590d30c129b3070dddce824c05bc165ac9802f0705cab6" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://fulcio.sigstore.dev", + "usage": "Fulcio" + } + } + }, + "rekor.pub": { + "length": 178, + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "custom": { + "sigstore": { + "status": "Active", + "uri": "https://rekor.sigstore.dev", + "usage": "Rekor" + } + } + }, + "trusted_root.json": { + "length": 7014, + "hashes": { + "sha256": "4364d7724c04cc912ce2a6c45ed2610e8d8d1c4dc857fb500292738d4d9c8d2c", + "sha512": "fdebade075c4840d40f1806a14d0660ae1d22f47c0516abc4141e09f4ddf6ee6f4dbfbf08a7025bea10a4b8794658a4cd8ebb1024b963f239a9bfe02c2057fc6" + } + } + }, + "delegations": { + "keys": { + "a89d235ee2f298d757438c7473b11b0b7b42ff1a45f1dfaac4c014183d6f8c45": { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoLrh0jmOfHWLwsyo/4oGbldF91WV\nfXvxVlDhW8fZwP/3vTnliBkDp5sH8/Dpm1SBOHkqENVt1+4Un/sFtl2zAQ==\n-----END PUBLIC KEY-----\n" + } + } + }, + "roles": [ + { + "name": "registry.npmjs.org", + "keyids": [ + "a89d235ee2f298d757438c7473b11b0b7b42ff1a45f1dfaac4c014183d6f8c45" + ], + "threshold": 1, + "terminating": true, + "paths": [ + "registry.npmjs.org/*" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/tuf-no-std/tests/timestamp.json b/tuf-no-std/tests/timestamp.json new file mode 100755 index 0000000..4ffd568 --- /dev/null +++ b/tuf-no-std/tests/timestamp.json @@ -0,0 +1,24 @@ +{ + "signed": { + "_type": "timestamp", + "spec_version": "1.0", + "version": 116, + "expires": "2023-09-26T16:08:35Z", + "meta": { + "snapshot.json": { + "length": 2304, + "hashes": { + "sha256": "6fac89d8e110d261e5194966578f665e60890183ef3be985b4062104eff13a7b", + "sha512": "7518beaa1a0920a3c17187d9ba93e0296187c5c2d0c393814620b0bb9ed6922189f4c7d0bcda9568861728e33a412ba39eaca172a814478d87c109e35877daa2" + }, + "version": 105 + } + } + }, + "signatures": [ + { + "keyid": "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a", + "sig": "3045022100b53cdca27e8135b4aca3bb3907089c4b95a38f35292b0cc9c7df18667eec870d02205dd5a7686f0222437f24cf48a8e11439fc55b88e548af6cd6cd8c10789e289ca" + } + ] +} \ No newline at end of file diff --git a/tuf-no-std/tuf-no-std-cli/Cargo.toml b/tuf-no-std/tuf-no-std-cli/Cargo.toml new file mode 100644 index 0000000..25e062a --- /dev/null +++ b/tuf-no-std/tuf-no-std-cli/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "tuf-no-std-cli" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tuf-no-std = { path = "../tuf-no-std", default-features = false, features = [ + "der", + "verify", + "ecdsa", + "rand", + "async", +] } +clap = { workspace = true, features = ["derive"] } +tuf-no-std-der = { path = "../tuf-no-std-der" } +pem = { workspace = true } +der = { workspace = true } +anyhow = { workspace = true, default-features = true } +serde = { version = "1.0.171", features = ["derive"] } +serde_yaml = { version = "0.9.33" } diff --git a/tuf-no-std/tuf-no-std-cli/src/args.rs b/tuf-no-std/tuf-no-std-cli/src/args.rs new file mode 100644 index 0000000..822ef39 --- /dev/null +++ b/tuf-no-std/tuf-no-std-cli/src/args.rs @@ -0,0 +1,8 @@ +use clap::Parser; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +pub struct Args { + /// Path to the config file. + pub config: PathBuf, +} diff --git a/tuf-no-std/tuf-no-std-cli/src/config.rs b/tuf-no-std/tuf-no-std-cli/src/config.rs new file mode 100644 index 0000000..43746d8 --- /dev/null +++ b/tuf-no-std/tuf-no-std-cli/src/config.rs @@ -0,0 +1,38 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; + +/// Data structure to parse/store configurations +#[derive(Debug, Deserialize, Serialize)] +pub struct Config { + /// key: role name, value: role configuration + pub roles: HashMap, + /// key: target name, value: target configuration + pub targets: HashMap, + /// directory to store the output + pub out: PathBuf, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TargetConfig { + pub name: String, + pub filepath: PathBuf, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RoleConfig { + pub threshold: u8, + pub version: u32, + pub keys: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct KeyConfig { + pub kind: KeyKind, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum KeyKind { + Ecdsa, +} diff --git a/tuf-no-std/tuf-no-std-cli/src/main.rs b/tuf-no-std/tuf-no-std-cli/src/main.rs new file mode 100644 index 0000000..30cee4a --- /dev/null +++ b/tuf-no-std/tuf-no-std-cli/src/main.rs @@ -0,0 +1,141 @@ +mod args; +mod config; + +use crate::args::Args; +use crate::config::{Config, KeyConfig, KeyKind}; +use anyhow::{Context, Result}; +use clap::Parser; +use der::Encode; +use std::fs::read; +use std::{ + fs, + fs::{read_to_string, OpenOptions}, + io::Write, +}; +use tuf_no_std::{ + builder::*, + common::crypto::sign::{Cipher, SigningKey}, +}; +use tuf_no_std_der::{ + root::Root, snapshot::Snapshot, targets::Targets, timestamp::Timestamp, Signed, +}; + +fn main() -> Result<()> { + let args = Args::parse(); + let config: Config = read_to_string(args.config) + .context("failed to config file") + .and_then(|s| serde_yaml::from_str(&s).context("failed to parse config file"))?; + + let root_config = &config.roles["root"]; + let timestamp_config = &config.roles["timestamp"]; + let snapshot_config = &config.roles["snapshot"]; + let targets_config = &config.roles["targets"]; + + let func = |k_conf: &KeyConfig| match k_conf.kind { + KeyKind::Ecdsa => SigningKey::new(Cipher::Ecdsa), + }; + let root_keys = root_config.keys.iter().map(func).collect::>(); + let timestamp_keys = timestamp_config.keys.iter().map(func).collect::>(); + let snapshot_keys = snapshot_config.keys.iter().map(func).collect::>(); + let targets_keys = targets_config.keys.iter().map(func).collect::>(); + + let root_keys_spki = root_keys + .iter() + .map(|k| k.as_spki().unwrap()) + .collect::>(); + let targets_keys_spki = targets_keys + .iter() + .map(|k| k.as_spki().unwrap()) + .collect::>(); + let snapshot_keys_spki = snapshot_keys + .iter() + .map(|k| k.as_spki().unwrap()) + .collect::>(); + let timestamp_keys_spki = timestamp_keys + .iter() + .map(|k| k.as_spki().unwrap()) + .collect::>(); + + let root = RootBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_role_and_key("root", &root_keys_spki, root_config.threshold) + .with_role_and_key("targets", &targets_keys_spki, targets_config.threshold) + .with_role_and_key("snapshot", &snapshot_keys_spki, snapshot_config.threshold) + .with_role_and_key( + "timestamp", + ×tamp_keys_spki, + timestamp_config.threshold, + ) + .with_version(root_config.version) + .build(); + let signed_root = Signed::::from_signed(root, &root_keys).unwrap(); + + let root_der = signed_root.to_der().unwrap(); + let rekor_pub_pem = config + .targets + .get("rekor") + .context("missing rekor target") + .and_then(|c| read(&c.filepath).context("failed to read rekor key"))?; + let ct_log_pub_pem = config + .targets + .get("ctlog") + .context("missing rekor target") + .and_then(|c| read(&c.filepath).context("failed to read rekor key"))?; + let fulcio_crt_pem = config + .targets + .get("fulcio") + .context("missing rekor target") + .and_then(|c| read(&c.filepath).context("failed to read rekor key"))?; + + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(targets_config.version) + .with_target(config.targets["rekor"].name.as_bytes(), &rekor_pub_pem) + .with_target(config.targets["fulcio"].name.as_bytes(), &fulcio_crt_pem) + .with_target(config.targets["ctlog"].name.as_bytes(), &ct_log_pub_pem) + .build(); + let signed_targets = Signed::::from_signed(targets, &targets_keys).unwrap(); + let targets_der = signed_targets.to_der().unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_meta(b"targets.der", targets_der.as_slice(), 1) + .with_version(snapshot_config.version) + .build(); + let signed_snapshot = Signed::::from_signed(snapshot, &snapshot_keys).unwrap(); + let snapshot_der = signed_snapshot.to_der().unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(snapshot_config.version) + .build(); + let signed_timestamp = Signed::::from_signed(timestamp, ×tamp_keys).unwrap(); + let timestamp_der = signed_timestamp.to_der().unwrap(); + let out_dir = config.out; + fs::create_dir_all(out_dir.join("targets")).expect("failed to create dir 'out/targets' dir"); + [ + (out_dir.join("1.root.der"), root_der.as_slice()), + (out_dir.join("1.targets.der"), targets_der.as_slice()), + (out_dir.join("1.timestamp.der"), timestamp_der.as_slice()), + (out_dir.join("1.snapshot.der"), snapshot_der.as_slice()), + (out_dir.join("targets/rekor.pub"), rekor_pub_pem.as_slice()), + ( + out_dir.join("targets/fulcio.crt.pem"), + fulcio_crt_pem.as_slice(), + ), + (out_dir.join("targets/ctlog.pub"), ct_log_pub_pem.as_slice()), + ] + .into_iter() + .for_each(|(p, f)| { + OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&p) + .unwrap_or_else(|_| panic!("failed to open file at path: {p:?}")) + .write_all(f) + .expect("failed to write to file"); + }); + Ok(()) +} diff --git a/tuf-no-std/tuf-no-std-common/Cargo.toml b/tuf-no-std/tuf-no-std-common/Cargo.toml new file mode 100755 index 0000000..6dc054b --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "tuf-no-std-common" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["sign", "verify"] +sign = ["p256/pem"] +verify = [] +rsa = ["dep:rsa"] +rand = ["dep:rand_core", "ed25519-dalek/rand_core"] +ecdsa = ["dep:p256"] +p256 = ["dep:p256"] +ed25519 = ["dep:ed25519", "dep:ed25519-dalek"] +dilithium3 = [ +# "dep:oqs" +] +composite = [] +async = ["dep:async-trait"] + +[dependencies] +serde = { workspace = true, features = ["derive"] } +#time = { version = "0.3", default-features = false, features = [ +# "serde", +# "parsing", +#] } +# semver = { version = "1.0", default-features = false, features = ["serde"] } +anyhow = { workspace = true, features = [] } +either = { workspace = true } +signature = { workspace = true, features = [] } +p256 = { workspace = true, features = ["ecdsa", "arithmetic", "pkcs8"], optional = true } +ed25519 = { workspace = true, optional = true } +ed25519-dalek = { workspace = true, features = ["pkcs8", "alloc"], optional = true } +rsa = { workspace = true, features = ["sha2"], optional = true } +pem-rfc7468 = { workspace = true, features = [] } +hex = { workspace = true, features = [] } +sha2 = { workspace = true, features = [] } +# oqs = { workspace = true, features = ["sigs", "non_portable"], optional = true } +spki = { workspace = true, features = ["fingerprint"] } +const-oid = { workspace = true, features = ["db"] } +der = { workspace = true, features = ["oid", "derive"] } +zeroize = { workspace = true, features = [] } +hash32 = { workspace = true } +rand_core = { workspace = true, features = ["getrandom"], optional = true } +async-trait = { workspace = true, optional = true } + + +[dev-dependencies] +serde_json = { workspace = true } +postcard = { workspace = true } +olpc-cjson = { workspace = true } +rand_core = { workspace = true, features = ["getrandom"] } +p256 = { workspace = true, features = ["ecdsa", "arithmetic", "pem"] } +hex-literal = { workspace = true } +anyhow = { workspace = true } +ring = { workspace = true } +untrusted = { workspace = true } +tough = { workspace = true } +rand = { workspace = true } +ed25519-dalek = { workspace = true, features = ["rand_core", "alloc", "pkcs8"] } diff --git a/tuf-no-std/tuf-no-std-common/src/common.rs b/tuf-no-std/tuf-no-std-common/src/common.rs new file mode 100644 index 0000000..2c764d5 --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/common.rs @@ -0,0 +1 @@ +pub type Threshold = u8; diff --git a/tuf-no-std/tuf-no-std-common/src/constants.rs b/tuf-no-std/tuf-no-std-common/src/constants.rs new file mode 100644 index 0000000..103b11d --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/constants.rs @@ -0,0 +1,15 @@ +use const_oid::ObjectIdentifier; + +pub const PEM_KEY_MAX_SIZE: usize = 1024; +pub const PEM_ECDSA_KEY_MAX_SIZE: usize = 130; + +pub const DER_DILITHIUM3_ECDSA_P256_SHA256_KEY_MAX_SIZE: usize = 2090; +pub const PEM_DILITHIUM3_ECDSA_P256_SHA256_KEY_MAX_SIZE: usize = 2884; +// https://github.com/IETF-Hackathon/pqc-certificates/blob/master/docs/oid_mapping.md +pub const ID_DILITHIUM3_ECDSA_P256_SHA256: ObjectIdentifier = + ObjectIdentifier::new_unwrap("2.16.840.1.114027.80.5.1.2"); +pub const ID_DILITHIUM3: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.2.267.12.6.5"); + +pub const ID_COMPOSITE_KEY_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("2.16.840.1.114027.80.4.1"); diff --git a/tuf-no-std/tuf-no-std-common/src/crypto/composite_spki.rs b/tuf-no-std/tuf-no-std-common/src/crypto/composite_spki.rs new file mode 100644 index 0000000..6730b13 --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/crypto/composite_spki.rs @@ -0,0 +1,116 @@ +use der::{AnyRef, Decode, Encode, FixedTag, Length, Reader, Tag, Writer}; + +pub(crate) struct PkComposite {} + +use crate::constants::*; +use crate::crypto::Dilithium3VerificationKey; +use der::asn1::{BitStringRef, SequenceOf}; +#[cfg(feature = "dilithium3")] +use oqs::sig::Algorithm::Dilithium3; +use signature::digest::Digest; +use signature::{DigestVerifier, Error, Verifier}; +use spki::{SubjectPublicKeyInfo, SubjectPublicKeyInfoRef}; + +pub type CompositeSubjectPublicKeyInfoRef<'a, const N: usize> = + SubjectPublicKeyInfo, CompositePublicKeyRef<'a, N>>; +pub type CompositePublicKeyRef<'a, const N: usize> = SequenceOf, N>; + +#[cfg(feature = "composite")] +pub enum CompositeVerificationKey { + #[cfg(all(feature = "dilithium3", feature = "ecdsa"))] + Dilithium3EcdsaP256Sha256 { + k1: p256::ecdsa::VerifyingKey, + k2: Dilithium3VerificationKey, + }, +} +#[cfg(feature = "composite")] +pub type CompositeSignatureRef<'a, const N: usize> = SequenceOf, N>; +#[cfg(feature = "composite")] +impl<'a> TryFrom> for CompositeVerificationKey { + type Error = (); + + fn try_from(value: CompositeSubjectPublicKeyInfoRef<'a, 2>) -> Result { + match value.algorithm.oid { + id if id == ID_DILITHIUM3_ECDSA_P256_SHA256 => { + let ecdsa_key = value + .subject_public_key + .iter() + .find(|k| k.algorithm.oid == const_oid::db::rfc5912::ID_EC_PUBLIC_KEY) + .ok_or(()) + .and_then(|k| p256::ecdsa::VerifyingKey::try_from(k.clone()).or(Err(())))?; + let dilithium3_key = value + .subject_public_key + .iter() + .find(|k| k.algorithm.oid == ID_DILITHIUM3) + .ok_or(()) + .and_then(|k| { + k.subject_public_key.as_bytes().ok_or(()).and_then(|b| { + oqs::sig::Sig::new(Dilithium3) + .unwrap() + .public_key_from_bytes(b) + .ok_or(()) + .map(|k| k.to_owned()) + }) + })?; + Ok(Self::Dilithium3EcdsaP256Sha256 { + k1: ecdsa_key, + k2: Dilithium3VerificationKey(dilithium3_key), + }) + } + _ => return Err(()), + } + } +} + +#[cfg(test)] +mod test { + use der::asn1::{BitStringRef, SequenceOf}; + use der::{AnyRef, Decode, DecodePem, Encode, EncodePem}; + use rand_core::OsRng; + use spki::{ + AlgorithmIdentifier, EncodePublicKey, SubjectPublicKeyInfo, SubjectPublicKeyInfoRef, + }; + + use crate::constants::*; + use crate::crypto::composite_spki::{ + CompositeSubjectPublicKeyInfoRef, CompositeVerificationKey, + }; + use oqs::*; + + #[test] + fn test() { + let signing_key = p256::ecdsa::SigningKey::random(&mut OsRng); // Serialize with `::to_bytes()` + let pubkey = p256::ecdsa::VerifyingKey::from(signing_key); + + let der = pubkey.to_public_key_der().unwrap(); + let spki = spki::SubjectPublicKeyInfoRef::from_der(der.as_ref()).unwrap(); + let mut keys = SequenceOf::new(); + keys.add(spki.clone()).unwrap(); + + oqs::init(); // Important: initialize liboqs + let sigalg = sig::Sig::new(sig::Algorithm::Dilithium3).unwrap(); + // A's long-term secrets + let (a_sig_pk, a_sig_sk) = sigalg.keypair().unwrap(); + let pk_bytes = a_sig_pk.as_ref(); + + keys.add(SubjectPublicKeyInfo { + algorithm: AlgorithmIdentifier { + oid: ID_DILITHIUM3, + parameters: None, + }, + subject_public_key: BitStringRef::from_bytes(pk_bytes).unwrap(), + }) + .unwrap(); + + let spki_new = CompositeSubjectPublicKeyInfoRef::<2> { + algorithm: AlgorithmIdentifier { + oid: ID_DILITHIUM3_ECDSA_P256_SHA256, + parameters: None, + }, + subject_public_key: keys, + }; + let composite_der = spki_new.to_pem(Default::default()).unwrap(); + eprintln!("{:#?}", composite_der.len()); + let key = CompositeVerificationKey::try_from(spki_new).expect("failed to get key"); + } +} diff --git a/tuf-no-std/tuf-no-std-common/src/crypto/mod.rs b/tuf-no-std/tuf-no-std-common/src/crypto/mod.rs new file mode 100644 index 0000000..37c52dc --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/crypto/mod.rs @@ -0,0 +1,55 @@ +#[cfg(feature = "dilithium3")] +pub mod composite_spki; +pub mod sign; + +#[cfg(feature = "dilithium3")] +use oqs::sig::PublicKeyRef; +use sha2::digest::Output; +use sha2::{Digest, Sha256}; + +// Disable this lint because there is not point in an Err type. +#[allow(clippy::result_unit_err)] +pub fn verify_sha256(hash: impl AsRef<[u8]>, data: impl AsRef<[u8]>) -> Result<(), ()> { + let decoded = hash.as_ref().into(); + verify_sha256_impl(decoded, data.as_ref()) +} + +fn verify_sha256_impl(hash: &Output, data: impl AsRef<[u8]>) -> Result<(), ()> { + let output = ::new().chain_update(data).finalize(); + if hash == &output { + Ok(()) + } else { + Err(()) + } +} + +#[cfg(feature = "dilithium3")] +fn from_pem_oqs<'o>( + pem: &[u8], + out: &'o mut [u8], + algo: oqs::sig::Algorithm, +) -> Result, ()> { + let scheme = oqs::sig::Sig::new(algo).or(Err(()))?; + let Ok((_label, data)) = pem_rfc7468::decode(pem, out) else { + return Err(()); // PEM decoding error + }; + let Ok((s, k)) = scheme.keypair() else { + panic!() + }; + scheme.public_key_from_bytes(data).ok_or(()) +} + +#[cfg(feature = "dilithium3")] +pub struct Dilithium3VerificationKey(pub(crate) oqs::sig::PublicKey); + +#[cfg(feature = "dilithium3")] +impl<'a> Verifier> for Dilithium3VerificationKey { + fn verify(&self, msg: &[u8], signature: &oqs::sig::SignatureRef) -> Result<(), Error> { + oqs::sig::Sig::new(oqs::sig::Algorithm::Dilithium3) + .or(Err(Default::default())) + .and_then(|alg| { + alg.verify(msg, signature, &self.0) + .or(Err(Default::default())) + }) + } +} diff --git a/tuf-no-std/tuf-no-std-common/src/crypto/sign.rs b/tuf-no-std/tuf-no-std-common/src/crypto/sign.rs new file mode 100644 index 0000000..8355e67 --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/crypto/sign.rs @@ -0,0 +1,100 @@ +use crate::TufError; +use crate::TufError::InternalError; +use alloc::vec::Vec; +use der::Decode; +use p256::ecdsa; +use signature::Signer; +use spki::{EncodePublicKey, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef}; + +#[derive(Debug, Clone)] +pub enum SigningKey { + #[cfg(feature = "ed25519")] + Ed25519Dalek(ed25519_dalek::SigningKey), + #[cfg(feature = "ecdsa")] + Ecdsa(ecdsa::SigningKey), +} +#[derive(Debug, Clone)] +pub enum Cipher { + #[cfg(feature = "ed25519")] + Ed25519Dalek, + #[cfg(feature = "ecdsa")] + Ecdsa, +} + +#[derive(Debug)] +pub enum RawSignature { + #[cfg(feature = "ed25519")] + Ed25519Dalek(ed25519_dalek::Signature), + #[cfg(feature = "ecdsa")] + Ecdsa(ecdsa::Signature), +} + +impl RawSignature { + pub fn to_vec(&self) -> Vec { + match self { + #[cfg(feature = "ed25519")] + RawSignature::Ed25519Dalek(sig) => sig.to_vec(), + #[cfg(feature = "ecdsa")] + RawSignature::Ecdsa(sig) => sig.to_bytes().to_vec(), + } + } +} + +#[cfg(feature = "sign")] +impl SigningKey { + #[cfg(feature = "rand")] + pub fn new(cipher: Cipher) -> Self { + match cipher { + #[cfg(feature = "ed25519")] + Cipher::Ed25519Dalek => { + Self::Ed25519Dalek(ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng)) + } + #[cfg(feature = "ecdsa")] + Cipher::Ecdsa => Self::Ecdsa(p256::ecdsa::SigningKey::random(&mut rand_core::OsRng)), + } + } + + pub fn sign(&self, msg: &[u8]) -> Result { + match self { + #[cfg(feature = "ed25519")] + SigningKey::Ed25519Dalek(key) => Ok(RawSignature::Ed25519Dalek(key.sign(msg))), + #[cfg(feature = "ecdsa")] + SigningKey::Ecdsa(key) => Ok(RawSignature::Ecdsa(key.sign(msg))), + } + } + pub fn key_id(&self) -> Result<[u8; 32], TufError> { + match self { + #[cfg(feature = "ed25519")] + SigningKey::Ed25519Dalek(key) => key + .verifying_key() + .to_public_key_der() + .map_err(|_| TufError::EncodingError) + .and_then(|d| { + SubjectPublicKeyInfoRef::from_der(d.as_bytes()) + .map_err(|_| InternalError) + .and_then(|spki| spki.fingerprint_bytes().map_err(|_| InternalError)) + }), + #[cfg(feature = "ecdsa")] + SigningKey::Ecdsa(key) => key + .verifying_key() + .to_public_key_der() + .map_err(|_| TufError::EncodingError) + .and_then(|d| { + SubjectPublicKeyInfoRef::from_der(d.as_bytes()) + .map_err(|_| InternalError) + .and_then(|spki| spki.fingerprint_bytes().map_err(|_| InternalError)) + }), + } + } + pub fn as_spki(&self) -> Result { + let pubkey_der = match self { + #[cfg(feature = "ed25519")] + SigningKey::Ed25519Dalek(key) => { + key.verifying_key().to_public_key_der().unwrap().into_vec() + } + #[cfg(feature = "ecdsa")] + SigningKey::Ecdsa(key) => key.verifying_key().to_public_key_der().unwrap().into_vec(), + }; + TryFrom::try_from(pubkey_der.as_slice()).map_err(|_| TufError::DecodingError) + } +} diff --git a/tuf-no-std/tuf-no-std-common/src/error.rs b/tuf-no-std/tuf-no-std-common/src/error.rs new file mode 100644 index 0000000..028b24f --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/error.rs @@ -0,0 +1,42 @@ +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum TufError { + // fewer signatures than threshold of old root -> arbitrary software attack possible + OldRootThresholdNotReached, + // fewer signatures than threshold of new root -> arbitrary software attack possible + ThresholdNotReached, + // new version number has to be exactly N+1 for old root with version number N -> roll back attack + InvalidNewVersionNumber, + // the client was not able to persistently store a new root file + CouldNotPersistRootMetadata, + // The expiration timestamp in the trusted root metadata file MUST be higher than the fixed update start time + // -> freeze attack + ExpiredRootFile, + ExpiredTimestampFile, + ExpiredSnapshotFile, + ExpiredTargetsFile, + // A fast-forward attack happens when attackers arbitrarily increase the version numbers of: (1) the timestamp metadata, (2) the snapshot metadata, and / or (3) the targets, or a delegated targets, metadata file in the snapshot metadata. + PotentialFastForwardAttack, + // could not fetch new data + FetchError, + InvalidSignature, + DecodingSignatureFailed, + DecodingPublicKeyFailed, + InternalError, + DecodingError, + EncodingError, + // could not find keys for a role + MissingSnapshotKeys, + MissingTargetsKeys, + MissingTimestampKeys, + MissingSnapshotFile, + MissingTargetsFile, + MissingTimestampFile, + MissingRootKeys, + NoSupportedHash, + MissingTargetMetadata, + InvalidHash, + InvalidTargetsInSnapshot, + InvalidLength, + NotEnoughSpace, + InvalidUtcTimestamp, +} diff --git a/tuf-no-std/tuf-no-std-common/src/lib.rs b/tuf-no-std/tuf-no-std-common/src/lib.rs new file mode 100755 index 0000000..179b72b --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/lib.rs @@ -0,0 +1,62 @@ +#![cfg_attr(not(test), no_std)] +extern crate alloc; + +pub mod common; +pub mod constants; +pub mod crypto; +pub mod error; +pub mod remote; +pub mod storage; + +use der::asn1::BitStringRef; +pub use error::TufError; + +pub type Version = u32; + +#[derive(Debug, Clone)] +pub enum RoleType { + Root, + Snapshot, + Targets, + Timestamp, +} + +impl<'a> PartialEq> for RoleType { + fn eq(&self, other: &BitStringRef<'a>) -> bool { + let name = match self { + RoleType::Root => "root", + RoleType::Snapshot => "snapshot", + RoleType::Targets => "targets", + RoleType::Timestamp => "timestamp", + }; + &BitStringRef::from_bytes(name.as_bytes()).unwrap() == other + } +} + +#[cfg(test)] +mod test { + use der::asn1::BitStringRef; + + use crate::RoleType; + + #[test] + fn test_eq() { + assert_eq!(RoleType::Root, BitStringRef::from_bytes(b"root").unwrap()); + assert_eq!( + RoleType::Snapshot, + BitStringRef::from_bytes(b"snapshot").unwrap() + ); + assert_eq!( + RoleType::Targets, + BitStringRef::from_bytes(b"targets").unwrap() + ); + assert_eq!( + RoleType::Timestamp, + BitStringRef::from_bytes(b"timestamp").unwrap() + ); + assert_ne!(RoleType::Root, BitStringRef::from_bytes(b"a").unwrap()); + assert_ne!(RoleType::Timestamp, BitStringRef::from_bytes(b"a").unwrap()); + assert_ne!(RoleType::Targets, BitStringRef::from_bytes(b"a").unwrap()); + assert_ne!(RoleType::Snapshot, BitStringRef::from_bytes(b"a").unwrap()); + } +} diff --git a/tuf-no-std/tuf-no-std-common/src/remote.rs b/tuf-no-std/tuf-no-std-common/src/remote.rs new file mode 100644 index 0000000..6abfb82 --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/remote.rs @@ -0,0 +1,54 @@ +use core::num::NonZeroU64; + +#[derive(Debug, Clone)] +pub enum TransportError { + /// Failed to fetch the specific file. For instance, if it was not present there. + FetchError, + /// Failed to connect to the remote host. + ConnectError, +} + +/// Trait that is used represent the communication with a remote TUF repository. +pub trait TufTransport { + fn fetch_root<'o>( + &self, + version: NonZeroU64, + out: &'o mut [u8], + ) -> Result<&'o [u8], TransportError>; + /// Fetches the most recent timestamp provided by the remote. + fn fetch_timestamp<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError>; + /// Fetches the most recent snapshot provided by the remote. + fn fetch_snapshot<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError>; + /// Fetches the most recent targets provided by the remote. + fn fetch_targets<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError>; + /// Fetches the target file with the name provided in `metapath`. + fn fetch_target_file<'o>( + &self, + metapath: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], TransportError>; +} + +/// Async version of the trait `TufTransport` trait that is used represent the communication with a remote TUF repository. +#[allow(async_fn_in_trait)] +#[cfg(feature = "async")] +pub trait TufTransportAsync { + /// Fetches the root file with the given version number. + async fn fetch_root<'o>( + &self, + version: NonZeroU64, + out: &'o mut [u8], + ) -> Result<&'o [u8], TransportError>; + /// Fetches the most recent timestamp provided by the remote. + async fn fetch_timestamp<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError>; + /// Fetches the most recent snapshot provided by the remote. + async fn fetch_snapshot<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError>; + /// Fetches the most recent targets provided by the remote. + async fn fetch_targets<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError>; + /// Fetches the target file with the name provided in `metapath`. + async fn fetch_target_file<'o>( + &self, + metapath: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], TransportError>; +} diff --git a/tuf-no-std/tuf-no-std-common/src/storage.rs b/tuf-no-std/tuf-no-std-common/src/storage.rs new file mode 100644 index 0000000..a7e05e5 --- /dev/null +++ b/tuf-no-std/tuf-no-std-common/src/storage.rs @@ -0,0 +1,193 @@ +use crate::TufError; + +/// A trait to abstract file storage for TUF. +pub trait TufStorage { + /// Delete currently stored timestamp metadata. + fn delete_timestamp_metadata(&mut self); + /// Delete currently stored snapshot metadata. + fn delete_snapshot_metadata(&mut self); + /// Persistently store the provided root file. This does overwrite the previous root file, this happens when it is committed. + fn persist_root(&mut self, data: &[u8]) -> Result<(), TufError>; + /// Persistently store the provided timestamp file. + fn persist_timestamp(&mut self, data: &[u8]) -> Result<(), TufError>; + /// Persistently store the provided snapshot file. + fn persist_snapshot(&mut self, data: &[u8]) -> Result<(), TufError>; + /// Persistently store the provided targets file. + fn persist_targets(&mut self, data: &[u8]) -> Result<(), TufError>; + /// Update the root file to the last root file that was persisted. + fn commit_root(&mut self) -> Result<(), TufError>; + fn current_root(&self) -> &[u8]; + fn current_root_copy<'o>(&self, out: &'o mut [u8]) -> &'o [u8]; + fn current_uncommitted_root_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]>; + fn current_uncommitted_root(&self) -> Option<&[u8]>; + fn current_timestamp(&self) -> Option<&[u8]>; + fn current_timestamp_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]>; + fn current_snapshot(&self) -> Option<&[u8]>; + fn current_snapshot_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]>; + fn current_targets(&self) -> Option<&[u8]>; +} + +pub enum StorageState { + Empty, + NonEmpty(usize), +} +/* +pub struct StackStorage<'a, const N: usize> { + buf: [[u8; N]; 5], + ref_storage: RefStorage<'a>, +} + +impl<'a, const N: usize> StackStorage<'a, N> { + pub fn new(buf: &'a mut [u8; N]) -> Self { + let (root, rest) = buf.split_at_mut(N / 5); + let (uncommitted_root, rest) = rest.split_at_mut(N / 5); + let (timestamp, rest) = rest.split_at_mut(N / 5); + let (snapshot, rest) = rest.split_at_mut(N / 5); + let (targets, _) = rest.split_at_mut(N / 5); + StackStorage { + buf: [[0u8; N]; 5], + ref_storage: RefStorage { + root: (0, root), + uncommitted_root: (StorageState::Empty, uncommitted_root), + timestamp: (StorageState::Empty, timestamp), + snapshot: (StorageState::Empty, snapshot), + targets: (StorageState::Empty, targets), + }, + } + } +} + +impl<'a, const N: usize> Storage<'a> for StackStorage<'a, N> { + fn delete_timestamp_metadata(&mut self) { + self.ref_storage.delete_timestamp_metadata() + } + fn delete_snapshot_metadata(&mut self) { + self.ref_storage.delete_snapshot_metadata() + } + fn persist_root(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + self.ref_storage.persist_root(data) + } + fn persist_timestamp(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + self.ref_storage.persist_timestamp(data) + } + fn persist_snapshot(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + self.ref_storage.persist_snapshot(data) + } + fn persist_targets(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + self.ref_storage.persist_targets(data) + } + fn commit_root(&mut self) -> Result<(), RootVerificationError> { + self.ref_storage.commit_root() + } + fn current_root<'o>(&self, out: &'o mut [u8]) -> &'o [u8] { + self.ref_storage.current_root(out) + } + fn current_uncommitted_root<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + self.ref_storage.current_uncommitted_root(out) + } + fn current_timestamp<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + self.ref_storage.current_timestamp(out) + } + fn current_snapshot<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + self.ref_storage.current_snapshot(out) + } +} + +pub struct RefStorage<'a> { + root: (usize, &'a mut [u8]), + uncommitted_root: (StorageState, &'a mut [u8]), + timestamp: (StorageState, &'a mut [u8]), + snapshot: (StorageState, &'a mut [u8]), + targets: (StorageState, &'a mut [u8]), +} + +impl<'a> RefStorage<'a> { + fn persist(dst: &mut (StorageState, &'a mut [u8]), src: &[u8]) -> Result<(), ()> { + if src.len() > dst.1.len() { + return Err(()); + } + dst.1[..src.len()].copy_from_slice(src); + dst.0 = StorageState::NonEmpty(src.len()); + Ok(()) + } +} + +impl<'a> Storage for RefStorage<'a> { + fn delete_timestamp_metadata(&mut self) { + self.timestamp.1.zeroize(); + self.timestamp.0 = StorageState::Empty; + } + + fn delete_snapshot_metadata(&mut self) { + self.snapshot.1.zeroize(); + self.snapshot.0 = StorageState::Empty; + } + + fn persist_root(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + RefStorage::persist(&mut self.uncommitted_root, data) + .map_err(|_| CouldNotPersistRootMetadata) + } + + fn persist_timestamp(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + RefStorage::persist(&mut self.timestamp, data).map_err(|_| CouldNotPersistRootMetadata) + } + + fn persist_snapshot(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + RefStorage::persist(&mut self.snapshot, data).map_err(|_| CouldNotPersistRootMetadata) + } + + fn persist_targets(&mut self, data: &[u8]) -> Result<(), RootVerificationError> { + RefStorage::persist(&mut self.targets, data).map_err(|_| CouldNotPersistRootMetadata) + } + + fn commit_root(&mut self) -> Result<(), RootVerificationError> { + let (StorageState::NonEmpty(size), data) = &self.uncommitted_root else { + return Ok(()); + }; + if self.root.1.len() < *size { + return Err(NotEnoughSpace); + } + self.root.1.zeroize(); + self.root.0 = *size; + self.root.1[0..*size].copy_from_slice(data); + Ok(()) + } + + fn current_root<'o>(&self, out: &'o mut [u8]) -> &'o [u8] { + let out = &mut out[..self.root.0]; + let src = &self.root.1[..self.root.0]; + out.copy_from_slice(src); + out + } + + fn current_uncommitted_root<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + let (StorageState::NonEmpty(size), data) = &self.uncommitted_root else { + return None; + }; + let out = &mut out[..*size]; + let src = &data[..*size]; + out.copy_from_slice(src); + Some(out) + } + + fn current_timestamp<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + let (StorageState::NonEmpty(size), data) = &self.timestamp else { + return None; + }; + let out = &mut out[..*size]; + let src = &data[..*size]; + out.copy_from_slice(src); + Some(out) + } + + fn current_snapshot<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + let (StorageState::NonEmpty(size), data) = &self.timestamp else { + return None; + }; + let out = &mut out[..*size]; + let src = &data[..*size]; + out.copy_from_slice(src); + Some(out) + } +} +*/ diff --git a/tuf-no-std/tuf-no-std-der/Cargo.toml b/tuf-no-std/tuf-no-std-der/Cargo.toml new file mode 100644 index 0000000..18ded58 --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "tuf-no-std-der" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + + +[features] +default = [] +sign = [] +verify = [] +rsa = ["dep:rsa"] +ecdsa = ["dep:p256"] +ed25519 = ["dep:ed25519", "dep:ed25519-dalek"] +dilithium3 = [ +# "dep:oqs" +] +composite = [] + +[dependencies] +tuf-no-std-common = { path = "../tuf-no-std-common" } +serde = { workspace = true, features = ["derive"] } +# time = { version = "0.3", default-features = false, features = [ +# "serde", +# "parsing", +# ] } +# semver = { version = "1.0", default-features = false, features = ["serde"] } +serde-json-core = { workspace = true } +anyhow = { workspace = true, features = [] } + +either = { workspace = true } +signature = { workspace = true, features = [] } +p256 = { workspace = true, features = ["ecdsa", "arithmetic"], optional = true } +ed25519 = { workspace = true, optional = true } +ed25519-dalek = { workspace = true, features = ["pkcs8"], optional = true } +rsa = { workspace = true, features = ["sha2"], optional = true } +# oqs = { workspace = true, features = ["sigs", "non_portable"], optional = true } +pem-rfc7468 = { workspace = true, features = [] } +hex = { workspace = true, features = [] } +sha2 = { workspace = true, features = [] } + +spki = { workspace = true, features = ["fingerprint"] } +const-oid = { workspace = true, features = ["db"] } +der = { workspace = true, features = ["oid", "derive"] } +zeroize = { workspace = true, features = [] } +hash32 = { workspace = true } + + +[dev-dependencies] +serde_json = { workspace = true } +postcard = { workspace = true } +olpc-cjson = { workspace = true } +rand_core = { workspace = true, features = ["getrandom"] } +p256 = { workspace = true, features = ["ecdsa", "arithmetic", "pem"] } +hex-literal = { workspace = true } +anyhow = { workspace = true } +ring = { workspace = true } +untrusted = { workspace = true } +tough = { workspace = true } +rand = { workspace = true } +ed25519-dalek = { workspace = true, features = ["rand_core", "alloc", "pkcs8"] } diff --git a/tuf-no-std/tuf-no-std-der/src/lib.rs b/tuf-no-std/tuf-no-std-der/src/lib.rs new file mode 100644 index 0000000..8f79455 --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/src/lib.rs @@ -0,0 +1,452 @@ +#![cfg_attr(not(test), no_std)] +extern crate alloc; + +use ::signature::DigestVerifier; +use alloc::vec::Vec; +use const_oid::db::rfc5912::ID_SHA_256; +use const_oid::ObjectIdentifier; +use core::fmt::Debug; +use core::marker::PhantomData; +use der::asn1::{BitString, BitStringRef, SequenceOf, UtcTime}; +use der::{Decode, Encode, Sequence}; +use spki::{ + AlgorithmIdentifier, AlgorithmIdentifierOwned, AlgorithmIdentifierRef, + SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef, +}; +use tuf_no_std_common::{TufError, Version}; + +pub mod role; +pub mod root; +pub mod signature; +pub mod snapshot; +pub mod targets; +pub mod timestamp; + +use crate::root::{Root, RootRef}; +use der::referenced::OwnedToRef; +#[cfg(feature = "dilithium3")] +use oqs::sig::Algorithm::Dilithium3; + +use sha2::digest::Digest; +use tuf_no_std_common::common::Threshold; +#[cfg(feature = "dilithium3")] +use tuf_no_std_common::crypto::composite_spki::{CompositePublicKeyRef, CompositeSignatureRef}; +use tuf_no_std_common::crypto::sign::SigningKey; + +pub type KeyId = BitString; + +pub type RawSignature = BitString; +pub type SpecVersion = BitString; +pub type KeyIdRef<'a> = BitStringRef<'a>; +pub type RawSignatureRef<'a> = BitStringRef<'a>; +pub type SpecVersionRef<'a> = BitStringRef<'a>; +pub type Scheme = AlgorithmIdentifierOwned; +pub type SchemeRef<'a> = AlgorithmIdentifierRef<'a>; +pub type KeyVal = SubjectPublicKeyInfoOwned; +pub type KeyValRef<'a> = SubjectPublicKeyInfoRef<'a>; +pub type DateTime = UtcTime; +pub type ConsistentSnapshot = bool; +/// Not using usize because the traits are not implemented for it +pub type Length = u64; + +/// DER encoded signature. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Signature { + /// SHA256 digest of the canonical form of the key. + pub keyid: KeyId, + /// Canonical encoding of the signature. + pub sig: RawSignature, +} + +/// Borrowed version of [Signature]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct SignatureRef<'a> { + /// SHA256 digest of the canonical form of the key. + pub keyid: KeyIdRef<'a>, + /// Canonical encoding of the signature. + pub sig: RawSignatureRef<'a>, +} + +impl OwnedToRef for Signature { + type Borrowed<'a> = SignatureRef<'a> where Self: 'a; + + fn owned_to_ref(&self) -> Self::Borrowed<'_> { + SignatureRef { + sig: self.sig.owned_to_ref(), + keyid: self.keyid.owned_to_ref(), + } + } +} + +/// Encoding of a TUF role key. +#[derive(Debug, Eq, PartialEq, Sequence)] +pub struct Key { + /// The OID of the key type. + keytype: ObjectIdentifier, + /// SPKI encoding of the public key. + keyval: KeyVal, + /// Algorithm identifier of the signature scheme. + scheme: Scheme, +} + +/// Borrowed version of [Key]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct KeyRef<'a> { + /// The OID of the key type. + keytype: ObjectIdentifier, + /// SPKI encoding of the public key. + keyval: KeyValRef<'a>, + /// Algorithm identifier of the signature scheme. + scheme: SchemeRef<'a>, +} + +impl<'a> KeyRef<'a> { + /// Key hashes are calculated from the DER encoded SPKI. + pub fn key_hash(&self) -> [u8; 32] { + self.keyval.fingerprint_bytes().unwrap() + } +} + +pub fn verify(spki: &SubjectPublicKeyInfoRef<'_>, msg: &[u8], sig: &[u8]) -> Result<(), TufError> { + match spki.algorithm.oid { + #[cfg(feature = "ed25519")] + const_oid::db::rfc8410::ID_ED_25519 => { + use ::signature::Verifier; + let key = ed25519_dalek::VerifyingKey::try_from(spki.clone()) + .map_err(|_| TufError::DecodingPublicKeyFailed)?; + let sig = sig + .try_into() + .map_err(|_| TufError::DecodingSignatureFailed) + .map(ed25519::Signature::from_bytes)?; + key.verify(msg, &sig) + .map_err(|_| TufError::InvalidSignature) + } + #[cfg(feature = "rsa")] + const_oid::db::rfc5912::ID_RSASSA_PSS => { + use ::signature::Verifier; + let sig = + rsa::pss::Signature::try_from(sig).or(Err(TufError::DecodingSignatureFailed))?; + rsa::RsaPublicKey::try_from(spki.clone()) + .map(Into::>::into) + .map_err(|_| TufError::DecodingPublicKeyFailed) + .and_then(|k| k.verify(msg, &sig).map_err(|_| TufError::InvalidSignature)) + } + #[cfg(feature = "ecdsa")] + const_oid::db::rfc5912::ID_EC_PUBLIC_KEY => { + let key = p256::ecdsa::VerifyingKey::try_from(spki.clone()) + .map_err(|_| TufError::DecodingPublicKeyFailed)?; + let mut hasher = ::new(); + Digest::update(&mut hasher, msg); + let sig = p256::ecdsa::Signature::from_bytes(sig.into()) + .map_err(|_| TufError::DecodingSignatureFailed)?; + key.verify_digest(hasher, &sig) + .or(Err(TufError::InvalidSignature))?; + Ok(()) + } + #[cfg(all(feature = "ecdsa", feature = "dilithium3"))] + tuf_no_std_common::constants::ID_DILITHIUM3_ECDSA_P256_SHA256 => { + let sigs = CompositeSignatureRef::<2>::from_der(sig).map_err(|_| ())?; + let d3 = oqs::sig::Sig::new(Dilithium3).unwrap(); + let ecdsa_sig = sigs + .iter() + .find_map(|s| p256::ecdsa::Signature::from_der(s.raw_bytes()).ok()) + .ok_or(())?; + let dilithium3_sig = sigs + .iter() + .find_map(|s| d3.signature_from_bytes(s.raw_bytes())) + .ok_or(())?; + + let spkis = spki + .subject_public_key + .as_bytes() + .ok_or(()) + .and_then(|bytes| CompositePublicKeyRef::<2>::from_der(bytes).map_err(|_| ()))?; + + spkis + .iter() + .find(|spki| spki.algorithm.oid == const_oid::db::rfc5912::ECDSA_WITH_SHA_256) + .ok_or(()) + .and_then(|spki| p256::ecdsa::VerifyingKey::try_from(spki.clone()).map_err(|_| ())) + .and_then(|key| { + let mut hasher = ::new(); + Digest::update(&mut hasher, msg); + key.verify_digest(hasher, &ecdsa_sig).or(Err(()))?; + Ok(()) + })?; + + spkis + .iter() + .find(|spki| spki.algorithm.oid == tuf_no_std_common::constants::ID_DILITHIUM3) + .and_then(|spki| d3.public_key_from_bytes(spki.subject_public_key.raw_bytes())) + .ok_or(()) + .and_then(|key| d3.verify(msg, dilithium3_sig, key).map_err(|_| ()))?; + Ok(()) + } + _ => { + unimplemented!( + "algorithm with OID {:?} is not implemented or activated", + spki.algorithm.oid + ) + } + } +} + +impl OwnedToRef for Key { + type Borrowed<'a> = KeyRef<'a> where Self: 'a; + + fn owned_to_ref(&self) -> Self::Borrowed<'_> { + KeyRef { + keytype: self.keytype, + keyval: self.keyval.owned_to_ref(), + scheme: self.scheme.owned_to_ref(), + } + } +} + +/// DER encoding of a TUF role. +#[derive(Debug, Eq, PartialEq, Sequence, Clone)] +pub struct Role { + /// The name of the role, e.g. "root". + pub name: BitString, + /// A collection of [KeyId]s that specify which keys are associated with this role. + pub keyids: Vec, + /// The signature threshold that needs to be reached for validity. + pub threshold: Threshold, +} + +impl OwnedToRef for Role { + type Borrowed<'a> = RoleRef<'a> where Self: 'a; + + fn owned_to_ref(&self) -> Self::Borrowed<'_> { + let keyids = self.keyids.iter().fold(SequenceOf::new(), |mut acc, a| { + acc.add(OwnedToRef::owned_to_ref(a)).expect("failed to add"); + acc + }); + RoleRef { + name: self.name.owned_to_ref(), + keyids, + threshold: self.threshold, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct RoleRef<'a> { + pub name: BitStringRef<'a>, + pub keyids: SequenceOf, 5>, + pub threshold: Threshold, +} + +/// DER encoding of a Hash +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Hash { + pub algorithm: AlgorithmIdentifierOwned, + pub value: BitString, +} + +impl Hash { + pub fn from_sha256_bytes(bytes: &[u8; 32]) -> Self { + Hash { + algorithm: AlgorithmIdentifier { + oid: const_oid::db::rfc5912::ID_SHA_256, + parameters: None, + }, + value: BitString::from_bytes(bytes).expect("failed to encode"), + } + } + /// Returns `Some` if this is a SHA-256 hash. + pub fn sha256(&self) -> Option<&[u8; 32]> { + if self.algorithm.oid != ID_SHA_256 { + return None; + } + self.value + .as_bytes() + .and_then(|b| TryFrom::try_from(b).ok()) + } +} + +impl<'a> HashRef<'a> { + /// Returns `Some` if this is a SHA-256 hash. + pub fn sha256(&self) -> Option<&[u8; 32]> { + if self.algorithm.oid != ID_SHA_256 { + return None; + } + self.value + .as_bytes() + .and_then(|b| TryFrom::try_from(b).ok()) + } +} + +/// Borrowed version of [Hash]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct HashRef<'a> { + pub algorithm: AlgorithmIdentifierRef<'a>, + pub value: BitStringRef<'a>, +} + +/// A signed TUF file. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Signed<'a, T: Debug + Eq + PartialEq + Decode<'a> + Encode> { + pub signatures: Vec, + pub signed: T, + pub _phantom: PhantomData<&'a ()>, +} + +impl<'a, T> Signed<'a, T> +where + T: Debug + Eq + PartialEq + Decode<'a> + Encode, +{ + /// Creates a signed object using the given signing keys from the data in `signed`. + pub fn from_signed(signed: T, signing_keys: &[SigningKey]) -> Result, TufError> { + let mut buf = [0u8; 2048]; + let encoded = signed + .encode_to_slice(&mut buf) + .map_err(|_| TufError::EncodingError)?; + let signatures = signing_keys + .iter() + .map(|key| { + key.sign(encoded).map(|sig| Signature { + keyid: key + .key_id() + .map(|key_id| BitString::from_bytes(&key_id).unwrap()) + .unwrap(), + sig: BitString::from_bytes(&sig.to_vec()).unwrap(), + }) + }) + .collect::, TufError>>()?; + Ok(Signed { + signatures, + _phantom: Default::default(), + signed, + }) + } + + pub fn encode_as_file<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TufError> { + self.encode_to_slice(out) + .map_err(|_| TufError::EncodingError) + } +} + +/// Borrowed version of [SignedRef]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct SignedRef<'a, T: Debug + Eq + PartialEq + Decode<'a> + Encode> { + pub signatures: SequenceOf, 5>, + pub signed: T, +} + +impl<'a, T> SignedRef<'a, T> +where + T: Debug + Eq + PartialEq + Decode<'a> + Encode, +{ + pub fn encode_as_file<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TufError> { + self.encode_to_slice(out) + .map_err(|_| TufError::EncodingError) + } +} + +/// Enum for signature schemes. +#[derive(Clone, Debug)] +pub enum SigningScheme { + Ed25519, + RsassaPss, + EcdsaSha256, + Dilithium3EcdsaP256Sha256, +} + +/// Enum for key types. +#[derive(Clone, Debug)] +pub enum KeyType { + Ed25519, + Ecdsa, + Dilithium3EcdsaP256Sha256, + Rsa, +} + +/// Get signing schemes from algorithm identifiers. +impl From for AlgorithmIdentifierOwned { + fn from(value: SigningScheme) -> Self { + match value { + SigningScheme::Ed25519 => AlgorithmIdentifier { + oid: const_oid::db::rfc8410::ID_ED_25519, + parameters: None, + }, + // This might need parameters. + SigningScheme::RsassaPss => AlgorithmIdentifier { + oid: const_oid::db::rfc5912::ID_RSASSA_PSS, + parameters: None, + }, + SigningScheme::EcdsaSha256 => AlgorithmIdentifier { + oid: const_oid::db::rfc5912::ECDSA_WITH_SHA_256, + parameters: None, + }, + SigningScheme::Dilithium3EcdsaP256Sha256 => AlgorithmIdentifier { + oid: tuf_no_std_common::constants::ID_DILITHIUM3_ECDSA_P256_SHA256, + parameters: None, + }, + } + } +} + +/// Get a key type from an OID that specifies the key type. +/// The relationship is usually specified in the standardization of the key type. +impl From for ObjectIdentifier { + fn from(value: KeyType) -> Self { + match value { + KeyType::Ed25519 => const_oid::db::rfc8410::ID_ED_25519, + KeyType::Ecdsa => const_oid::db::rfc5912::SECP_256_R_1, + KeyType::Dilithium3EcdsaP256Sha256 => { + tuf_no_std_common::constants::ID_DILITHIUM3_ECDSA_P256_SHA256 + } + KeyType::Rsa => const_oid::db::rfc5912::RSA_ENCRYPTION, + } + } +} + +impl<'b> OwnedToRef for Signed<'b, Root> { + type Borrowed<'a> = SignedRef<'a, RootRef<'a>> where Self: 'a; + + fn owned_to_ref(&self) -> Self::Borrowed<'_> { + let mut sigs = SequenceOf::new(); + for sig in self.signatures.iter() { + sigs.add(sig.owned_to_ref()).unwrap() + } + SignedRef { + signatures: sigs, + signed: self.signed.owned_to_ref(), + } + } +} +pub struct TufDer(); + +#[cfg(test)] +mod test { + use crate::{Key, KeyType, SigningScheme}; + use der::referenced::OwnedToRef; + use der::Decode; + use ed25519_dalek::SigningKey; + use rand::rngs::OsRng; + use spki::{EncodePublicKey, SubjectPublicKeyInfoOwned}; + + #[test] + fn test_key_hash() { + let mut csprng = OsRng; + + let root_key: SigningKey = SigningKey::generate(&mut csprng); + let root_pub_spki = SubjectPublicKeyInfoOwned::from_der( + root_key + .verifying_key() + .to_public_key_der() + .unwrap() + .as_bytes(), + ) + .unwrap(); + let root_key_id = root_pub_spki.fingerprint_bytes().unwrap(); + let key = Key { + keytype: KeyType::Ed25519.into(), + keyval: root_pub_spki, + scheme: SigningScheme::Ed25519.into(), + }; + + assert_eq!(root_key_id, key.owned_to_ref().key_hash(),) + } +} diff --git a/tuf-no-std/tuf-no-std-der/src/role.rs b/tuf-no-std/tuf-no-std-der/src/role.rs new file mode 100644 index 0000000..e44e694 --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/src/role.rs @@ -0,0 +1,17 @@ +use der::Enumerated; + +/// +/// ```der +/// Root ::= ENUMERATED {root (0), +// timestamp (1), +// snapshot (2), +// target (3)} +/// ``` +#[derive(Enumerated, Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum Role { + Root = 0, + Timestamp = 1, + Snapshot = 2, + Target = 3, +} diff --git a/tuf-no-std/tuf-no-std-der/src/root.rs b/tuf-no-std/tuf-no-std-der/src/root.rs new file mode 100644 index 0000000..fe388f2 --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/src/root.rs @@ -0,0 +1,63 @@ +use crate::{Role, RoleRef, SpecVersion, SpecVersionRef}; +use alloc::vec::Vec; +use core::fmt::Debug; +use der::asn1::{SequenceOf, UtcTime}; +use der::referenced::OwnedToRef; +use der::Sequence; +use spki::{SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef}; +use tuf_no_std_common::Version; + +/// DER encoding of a TUF root file. +#[derive(Debug, Eq, PartialEq, Sequence, Clone)] +pub struct Root { + /// Not implemented, refer to the [TUF specification section on consistent snapshots](https://theupdateframework.github.io/specification/latest/#consistent-snapshots). + pub consistent_snapshot: bool, + pub expires: UtcTime, + // Keys sorted by key ID. + pub keys: Vec, + /// Roles sorted by name. + pub roles: Vec, + /// Version of the TUF spec. + pub spec_version: SpecVersion, + /// Version of the root file. + pub version: Version, +} + +/// Borrowed version of [Root]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct RootRef<'a> { + /// Not implemented, refer to the [TUF specification section on consistent snapshots](https://theupdateframework.github.io/specification/latest/#consistent-snapshots). + pub consistent_snapshot: bool, + pub expires: UtcTime, + // Keys sorted by key ID. + pub keys: SequenceOf, 7>, + // sorted by name + pub roles: SequenceOf, 4>, + /// Version of the TUF spec. + pub spec_version: SpecVersionRef<'a>, + /// Version of the root file. + pub version: Version, +} + +impl OwnedToRef for Root { + type Borrowed<'a> = RootRef<'a> where Self: 'a; + + fn owned_to_ref(&self) -> Self::Borrowed<'_> { + let mut keys: SequenceOf = Default::default(); + let mut roles: SequenceOf = Default::default(); + self.keys + .iter() + .for_each(|k| keys.add(k.owned_to_ref()).unwrap()); + self.roles + .iter() + .for_each(|r| roles.add(r.owned_to_ref()).unwrap()); + RootRef { + consistent_snapshot: self.consistent_snapshot, + expires: self.expires, + keys, + roles, + spec_version: self.spec_version.owned_to_ref(), + version: self.version, + } + } +} diff --git a/tuf-no-std/tuf-no-std-der/src/signature.rs b/tuf-no-std/tuf-no-std-der/src/signature.rs new file mode 100644 index 0000000..b12fa37 --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/src/signature.rs @@ -0,0 +1,3 @@ +use der::asn1::BitStringRef; + +pub type SignatureRef<'a> = BitStringRef<'a>; diff --git a/tuf-no-std/tuf-no-std-der/src/snapshot.rs b/tuf-no-std/tuf-no-std-der/src/snapshot.rs new file mode 100644 index 0000000..79de09f --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/src/snapshot.rs @@ -0,0 +1,49 @@ +use crate::{Hash, HashRef, SpecVersion, SpecVersionRef, Version}; +use alloc::vec::Vec; +use core::fmt::Debug; +use der::asn1::{BitString, BitStringRef, SequenceOf, UtcTime}; +use der::Sequence; + +/// DER encoding of a TUF snapshot file. +/// [Refer to the TUF specification.](https://theupdateframework.github.io/specification/latest/#file-formats-snapshot) +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Snapshot { + pub expires: UtcTime, + pub meta: Vec, + pub spec_version: SpecVersion, + pub version: Version, +} + +/// [Refer to the TUF specification](https://theupdateframework.github.io/specification/latest/#metafiles). +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct SnapshotMeta { + /// Identifier/metapath of the file. + pub metapath: BitString, + pub length: u64, + /// Version of the file specified in this struct. + pub version: Version, + /// Hashes of the file. + pub hashes: Vec, +} + +/// Borrowed version of [Snapshot]. +/// [Refer to the TUF specification.](https://theupdateframework.github.io/specification/latest/#file-formats-snapshot) +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct SnapshotRef<'a> { + pub expires: UtcTime, + pub meta: SequenceOf, 1>, + pub spec_version: SpecVersionRef<'a>, + pub version: Version, +} + +/// Borrowed version of [SnapshotMeta]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct SnapshotMetaRef<'a> { + /// Identifier/metapath of the file. + pub metapath: BitStringRef<'a>, + pub length: u64, + /// Version of the file specified in this struct. + pub version: Version, + /// Hashes of the file. Hard coded to size 1 to reduce stack usage. + pub hashes: SequenceOf, 1>, +} diff --git a/tuf-no-std/tuf-no-std-der/src/targets.rs b/tuf-no-std/tuf-no-std-der/src/targets.rs new file mode 100644 index 0000000..4a9197d --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/src/targets.rs @@ -0,0 +1,88 @@ +use crate::{Hash, HashRef, Length, SpecVersion, SpecVersionRef, Version}; +use alloc::vec::Vec; +use core::fmt::Debug; +use der::asn1::{BitString, BitStringRef, SequenceOf, UtcTime}; +use der::{Any, AnyRef, Sequence}; + +pub type TargetPath = BitString; +pub type TargetPathRef<'a> = BitStringRef<'a>; + +/// DER encoding of the TUF targets file format. +/// [Refer to the TUF specification.](https://theupdateframework.github.io/specification/latest/#file-formats-targets) +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Targets { + pub expires: UtcTime, + pub spec_version: SpecVersion, + /// Version of this targets file. + pub version: Version, + /// Targets specified in this targets file. + pub targets: Vec, + /// Delegations are not implemented. + pub delegations: Vec, +} + +/// Borrowed version of [Targets]. +/// [Refer to the TUF specification.](https://theupdateframework.github.io/specification/latest/#file-formats-targets) +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct TargetsRef<'a, const N_TARGETS: usize, const N_DELEGATIONS: usize> { + pub expires: UtcTime, + pub spec_version: SpecVersionRef<'a>, + /// Version of this targets file. + pub version: Version, + /// Targets specified in this targets file. + pub targets: SequenceOf, N_TARGETS>, + /// Delegations are not implemented. + pub delegations: SequenceOf, +} + +pub type Delegation = (); +pub type DelegationRef<'a> = (); + +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Target { + /// Used to identify the target file. + pub name: TargetPath, + /// The inner part of the data, containing the actual information. + pub value: TargetValue, +} + +/// Borrowed version of [Target]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct TargetRef<'a> { + /// Used to identify the target file. + pub metapath: TargetPathRef<'a>, + /// Used to identify the target file. + pub value: TargetValueRef<'a>, +} + +/// Information about a target file. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct TargetValue { + pub hashes: Vec, + pub length: Option, + pub custom: Option, +} + +/// Borrowed version of [TargetValue]. +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct TargetValueRef<'a> { + // not using usize because the traits are not implemented for it + pub hashes: SequenceOf, 2>, + pub length: Option, + pub custom: Option>, +} + +/// [Refer to the TUF specification](https://theupdateframework.github.io/specification/latest/#custom). +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Custom { + pub name: BitString, + pub value: Any, +} + +/// Borrowed version of [Custom]. +/// [Refer to the TUF specification](https://theupdateframework.github.io/specification/latest/#custom). +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct CustomRef<'a> { + pub name: BitStringRef<'a>, + pub value: AnyRef<'a>, +} diff --git a/tuf-no-std/tuf-no-std-der/src/timestamp.rs b/tuf-no-std/tuf-no-std-der/src/timestamp.rs new file mode 100644 index 0000000..d4ff7aa --- /dev/null +++ b/tuf-no-std/tuf-no-std-der/src/timestamp.rs @@ -0,0 +1,26 @@ +use crate::snapshot::{SnapshotMeta, SnapshotMetaRef}; +use crate::{SpecVersion, SpecVersionRef}; +use core::fmt::Debug; +use der::asn1::UtcTime; +use der::Sequence; +use tuf_no_std_common::Version; + +/// DER encoding of the TUF Timestamp format. +/// [Refer to the TUF specification.](https://theupdateframework.github.io/specification/latest/#file-formats-timestamp) +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Timestamp { + pub expires: UtcTime, + pub meta: SnapshotMeta, + pub spec_version: SpecVersion, + pub version: Version, +} + +/// Borrowed version of [Timestamp]. +/// [Refer to the TUF specification.](https://theupdateframework.github.io/specification/latest/#file-formats-timestamp) +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct TimestampRef<'a> { + pub expires: UtcTime, + pub meta: SnapshotMetaRef<'a>, + pub spec_version: SpecVersionRef<'a>, + pub version: Version, +} diff --git a/tuf-no-std/tuf-no-std/Cargo.toml b/tuf-no-std/tuf-no-std/Cargo.toml new file mode 100644 index 0000000..3dee726 --- /dev/null +++ b/tuf-no-std/tuf-no-std/Cargo.toml @@ -0,0 +1,102 @@ +[package] +name = "tuf-no-std" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[features] +default = [ + "der", + "sign", + "rsa", + "ecdsa", + "ed25519", + "composite", + "rand", + "async", +] +memory-transport = [] +rand = ["tuf-no-std-common/rand"] +async = ["tuf-no-std-common/async"] +der = ["dep:tuf-no-std-der"] +sign = [ + "tuf-no-std-common/sign", + "tuf-no-std-der/sign", +] +verify = [ + "tuf-no-std-common/verify", + "tuf-no-std-der/verify", +] +rsa = [ + "tuf-no-std-common/rsa", + "tuf-no-std-der/rsa", +] +ecdsa = [ + "dep:p256", + "tuf-no-std-common/ecdsa", + "tuf-no-std-der/ecdsa", +] +ed25519 = [ + "dep:ed25519", + "dep:ed25519-dalek", + "tuf-no-std-common/ed25519", + "tuf-no-std-der/ed25519", +] +dilithium3 = [ + "tuf-no-std-common/dilithium3", + "tuf-no-std-der/dilithium3", +] +composite = [ + "tuf-no-std-common/composite", + "tuf-no-std-der/composite", +] + + +[dependencies] +tuf-no-std-common = { path = "../tuf-no-std-common" } +tuf-no-std-der = { path = "../tuf-no-std-der", optional = true } + + +serde = { workspace = true, features = ["derive"] } +heapless = { workspace = true } + +#time = { version = "0.3", default-features = false, features = [ +# "serde", +# "parsing", +#] } +# semver = { version = "1.0", default-features = false, features = ["serde"] } + +serde-json-core = { workspace = true } +anyhow = { workspace = true, features = [] } + +either = { workspace = true } +signature = { workspace = true, features = [] } +p256 = { workspace = true, features = ["ecdsa", "arithmetic"], optional = true } +ed25519 = { workspace = true, optional = true } +ed25519-dalek = { workspace = true, features = ["pkcs8"], optional = true } + +pem-rfc7468 = { workspace = true, features = [] } +hex = { workspace = true, features = [] } +sha2 = { workspace = true, features = [] } + +spki = { workspace = true, features = ["fingerprint"] } +const-oid = { workspace = true, features = ["db"] } +der = { workspace = true, features = ["oid", "derive"] } +zeroize = { workspace = true, features = [] } +hash32 = { workspace = true } + + +[dev-dependencies] +serde_json = { workspace = true } +postcard = { workspace = true } +olpc-cjson = { workspace = true } +rand_core = { workspace = true, features = ["getrandom"] } +p256 = { workspace = true, features = ["ecdsa", "arithmetic", "pem"] } +hex-literal = { workspace = true } +anyhow = { workspace = true } +ring = { workspace = true } +untrusted = { workspace = true } +tough = { workspace = true } +rand = { workspace = true } +ed25519-dalek = { workspace = true, features = ["rand_core", "alloc", "pkcs8"] } +tokio = { workspace = true, features = ["full"] } diff --git a/tuf-no-std/tuf-no-std/src/builder.rs b/tuf-no-std/tuf-no-std/src/builder.rs new file mode 100644 index 0000000..5f73228 --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/builder.rs @@ -0,0 +1,363 @@ +use der::asn1::{BitString, UtcTime}; +use der::{DateTime, Encode}; +use sha2::{Digest, Sha256}; +use spki::SubjectPublicKeyInfo; +use tuf_no_std_common::Version; +use tuf_no_std_der::targets::TargetValue; +use tuf_no_std_der::Signed; +#[cfg(feature = "der")] +use tuf_no_std_der::{ + root::Root, + snapshot::{Snapshot, SnapshotMeta}, + targets::{Delegation, Target, Targets}, + timestamp::Timestamp, + {Hash, Role}, +}; + +pub struct RootBuilder { + root: Root, +} + +impl Default for RootBuilder { + fn default() -> Self { + RootBuilder { + root: Root { + consistent_snapshot: false, + expires: UtcTime::from_date_time(DateTime::new(1970, 1, 1, 0, 0, 0).unwrap()) + .unwrap(), + keys: Default::default(), + roles: Default::default(), + spec_version: BitString::from_bytes(b"1.0").unwrap(), + version: 0, + }, + } + } +} + +impl RootBuilder { + /// Set the expiration date of the root role to this [UTC date](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). + pub fn with_expiration_utc( + mut self, + year: u16, + month: u8, + day: u8, + hour: u8, + minutes: u8, + seconds: u8, + ) -> Self { + self.root.expires = UtcTime::from_date_time( + DateTime::new(year, month, day, hour, minutes, seconds).unwrap(), + ) + .unwrap(); + self + } + + /// Add add a role to the root file. + pub fn with_role(mut self, role: Role) -> Self { + self.root.roles.push(role); + self + } + + /// Add a key to the root file. + pub fn with_key(mut self, key: SubjectPublicKeyInfo) -> Self { + self.root.keys.push(key); + self + } + + /// Add a root role that has the given key. Useful to add roles with a single key. + pub fn with_role_and_key( + self, + role: &str, + keys: &[SubjectPublicKeyInfo], + threshold: u8, + ) -> Self { + let key_ids = keys + .iter() + .map(|key| key.fingerprint_bytes().unwrap()) + .map(|key_id| BitString::from_bytes(key_id.as_slice()).unwrap()) + .collect(); + + let builder = self.with_role(Role { + name: BitString::from_bytes(role.as_bytes()).unwrap(), + keyids: key_ids, + threshold, + }); + keys.iter() + .fold(builder, |builder, key| builder.with_key(key.clone())) + } + + /// Set the version of the root file. + pub fn with_version(mut self, version: u32) -> Self { + self.root.version = version; + self + } + + /// Set the flag whether [consistent snapshots](https://theupdateframework.github.io/specification/latest/#consistent-snapshots) are enabled. + pub fn consistent_snapshot(mut self, consistent_snapshots: bool) -> Self { + self.root.consistent_snapshot = consistent_snapshots; + self + } + + /// Return the constructed root file.. + pub fn build(self) -> Root { + self.root + } +} + +pub struct TimestampBuilder { + inner: Timestamp, +} + +impl Default for TimestampBuilder { + fn default() -> Self { + TimestampBuilder { + inner: Timestamp { + expires: UtcTime::from_date_time(DateTime::new(1970, 1, 1, 0, 0, 0).unwrap()) + .unwrap(), + meta: SnapshotMeta { + metapath: BitString::from_bytes(b"snapshot.der").unwrap(), + length: 0, + version: 0, + hashes: Default::default(), + }, + spec_version: BitString::from_bytes(b"1.0").unwrap(), + version: 0, + }, + } + } +} + +impl TimestampBuilder { + /// Add a snapshot file to the timestamp file. + pub fn with_snapshot(mut self, metapath: &str, snapshot: &Signed) -> Self { + let encoded_snapshot = snapshot.to_der().unwrap(); + let hash = Hash::from_sha256_bytes( + &::new() + .chain_update(encoded_snapshot) + .finalize() + .into(), + ); + let length = snapshot + .encoded_len() + .expect("failed to get encoded length"); + self.inner.meta.metapath = BitString::from_bytes(metapath.as_bytes()).unwrap(); + self.inner.meta.length = Into::::into(length).into(); + self.inner.meta.hashes = [hash].to_vec(); + self.inner.meta.version = snapshot.signed.version; + self + } + + /// Set the expiration date of the timestamp role to this [UTC date](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). + pub fn with_expiration_utc( + mut self, + year: u16, + month: u8, + day: u8, + hour: u8, + minutes: u8, + seconds: u8, + ) -> Self { + self.inner.expires = UtcTime::from_date_time( + DateTime::new(year, month, day, hour, minutes, seconds).unwrap(), + ) + .unwrap(); + self + } + + /// Set the version of the timestamp file. Has to increase between iterations. + pub fn with_version(mut self, version: Version) -> Self { + self.inner.version = version; + self + } + + /// Finish the construction. + pub fn build(self) -> Timestamp { + self.inner + } +} + +pub struct SnapshotBuilder { + inner: Snapshot, +} + +impl Default for SnapshotBuilder { + fn default() -> Self { + SnapshotBuilder { + inner: Snapshot { + expires: UtcTime::from_date_time(DateTime::new(1970, 1, 1, 0, 0, 0).unwrap()) + .unwrap(), + meta: Default::default(), + spec_version: BitString::from_bytes(b"1.0").unwrap(), + version: 0, + }, + } + } +} + +impl SnapshotBuilder { + /// Set the expiration date of the snapshot role to this [UTC date](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). + pub fn with_expiration_utc( + mut self, + year: u16, + month: u8, + day: u8, + hour: u8, + minutes: u8, + seconds: u8, + ) -> Self { + self.inner.expires = UtcTime::from_date_time( + DateTime::new(year, month, day, hour, minutes, seconds).unwrap(), + ) + .unwrap(); + self + } + + /// Set the version of the snapshot file. Has to increase between iterations. + pub fn with_version(mut self, version: Version) -> Self { + self.inner.version = version; + self + } + pub fn with_meta(mut self, metapath: &[u8], data: &[u8], version: Version) -> Self { + let hash = &::new() + .chain_update(data) + .finalize() + .into(); + self.inner.meta.push(SnapshotMeta { + metapath: BitString::from_bytes(metapath).unwrap(), + hashes: [Hash::from_sha256_bytes(hash)].to_vec(), + length: data.len() as u64, + version, + }); + + self + } + pub fn build(self) -> Snapshot { + self.inner + } +} + +pub struct TargetsBuilder { + inner: Targets, +} + +impl Default for TargetsBuilder { + fn default() -> Self { + TargetsBuilder { + inner: Targets { + expires: UtcTime::from_date_time(DateTime::new(1970, 1, 1, 0, 0, 0).unwrap()) + .unwrap(), + spec_version: BitString::from_bytes(b"1.0").unwrap(), + version: 0, + targets: Default::default(), + delegations: Default::default(), + }, + } + } +} + +impl TargetsBuilder { + /// Add target file to the targets role. + pub fn with_target(mut self, name: &[u8], target_file: &[u8]) -> Self { + let hash = &::new() + .chain_update(target_file) + .finalize() + .into(); + let target = Target { + name: BitString::from_bytes(name).unwrap(), + value: TargetValue { + length: Some(target_file.len() as u64), + hashes: [Hash::from_sha256_bytes(hash)].to_vec(), + custom: None, + }, + }; + self.inner.targets.push(target); + self + } + + /// Delegations are not implemented. + pub fn with_delegation(self, _delegation: Delegation) -> Self { + unimplemented!("delegations are not implemented") + } + + /// Set the expiration date of the targets role to this [UTC date](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). + pub fn with_expiration_utc( + mut self, + year: u16, + month: u8, + day: u8, + hour: u8, + minutes: u8, + seconds: u8, + ) -> Self { + self.inner.expires = UtcTime::from_date_time( + DateTime::new(year, month, day, hour, minutes, seconds).unwrap(), + ) + .unwrap(); + self + } + + /// Set the version of the targets file. Has to increase between iterations. + pub fn with_version(mut self, version: Version) -> Self { + self.inner.version = version; + self + } + + /// Finish the construction. + pub fn build(self) -> Targets { + self.inner + } +} + +#[cfg(test)] +mod test { + use crate::builder::RootBuilder; + use alloc::vec; + use der::asn1::{BitString, UtcTime}; + use der::Decode; + use ed25519_dalek::SigningKey; + use rand_core::OsRng; + use spki::{EncodePublicKey, SubjectPublicKeyInfoOwned}; + use tuf_no_std_der::root::Root; + use tuf_no_std_der::Role; + + #[test] + fn test_root_builder() { + let mut csprng = OsRng; + let root_key: SigningKey = SigningKey::generate(&mut csprng); + let root_pub_spki = SubjectPublicKeyInfoOwned::from_der( + root_key + .verifying_key() + .to_public_key_der() + .unwrap() + .as_bytes(), + ) + .unwrap(); + let root_key_id = root_pub_spki.fingerprint_bytes().unwrap(); + + let output = RootBuilder::default() + .with_key(root_pub_spki.clone()) + .with_role(Role { + name: BitString::from_bytes(b"root").unwrap(), + keyids: vec![BitString::from_bytes(&root_key_id).unwrap()], + threshold: 1, + }) + .with_expiration_utc(2023, 1, 1, 1, 1, 1) + .with_version(1) + .build(); + + let expected = Root { + consistent_snapshot: false, + expires: UtcTime::from_date_time(der::DateTime::new(2023, 1, 1, 1, 1, 1).unwrap()) + .unwrap(), + keys: vec![root_pub_spki], + roles: vec![Role { + keyids: vec![BitString::from_bytes(&root_key_id).unwrap()], + threshold: 1, + name: BitString::from_bytes(b"root").unwrap(), + }], + spec_version: BitString::from_bytes(b"1.0").unwrap(), + version: 1, + }; + assert_eq!(expected, output); + } +} diff --git a/tuf-no-std/tuf-no-std/src/canonical.rs b/tuf-no-std/tuf-no-std/src/canonical.rs new file mode 100644 index 0000000..00278e1 --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/canonical.rs @@ -0,0 +1,12 @@ +#[derive(Debug)] +pub enum EncodingError { + BufferTooSmall, +} + +/// Trait used to abstract files being encoded canonically. +pub trait EncodeCanonically { + /// Encodes the file canonically. + /// It is important to note that this **does never** encode the + /// `signatures` field, as it is not used as part of the canonical encoding. + fn encode_canonically<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], EncodingError>; +} diff --git a/tuf-no-std/tuf-no-std/src/format.rs b/tuf-no-std/tuf-no-std/src/format.rs new file mode 100644 index 0000000..5708a79 --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/format.rs @@ -0,0 +1,32 @@ +use crate::role::root::TufRoot; +use crate::role::targets::TufTargets; +use crate::role::{DecodeRole, RoleUpdate, SignedFile, TufSnapshot, TufTimestamp}; +#[cfg(feature = "der")] +use spki::SubjectPublicKeyInfoRef; +#[cfg(feature = "der")] +use tuf_no_std_der::{ + root::RootRef, + snapshot::SnapshotRef, + targets::TargetsRef, + timestamp::TimestampRef, + {SignedRef, TufDer}, +}; + +/// Trait used to abstract a file format that can be used to implement TUF. +/// An important constraint is that it has to support canonical encoding. +pub trait TufFormat<'a> { + type Root: TufRoot + RoleUpdate + DecodeRole<'a> + SignedFile + Clone; + type Timestamp: TufTimestamp + DecodeRole<'a> + RoleUpdate + SignedFile + Clone; + type Snapshot: TufSnapshot + DecodeRole<'a> + RoleUpdate + SignedFile + Clone; + type Targets: TufTargets + DecodeRole<'a> + RoleUpdate + SignedFile + Clone; + type Key; +} + +#[cfg(feature = "der")] +impl<'a> TufFormat<'a> for TufDer { + type Root = SignedRef<'a, RootRef<'a>>; + type Timestamp = SignedRef<'a, TimestampRef<'a>>; + type Snapshot = SignedRef<'a, SnapshotRef<'a>>; + type Targets = SignedRef<'a, TargetsRef<'a, 4, 0>>; + type Key = SubjectPublicKeyInfoRef<'a>; +} diff --git a/tuf-no-std/tuf-no-std/src/lib.rs b/tuf-no-std/tuf-no-std/src/lib.rs new file mode 100644 index 0000000..54a405c --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/lib.rs @@ -0,0 +1,191 @@ +#![cfg_attr(not(test), no_std)] +//! A no `no-std` rust implementation for TUF based on the [DER format](https://en.wikipedia.org/wiki/X.690#DER_encoding). +//! This implementation aims to fulfill the [TUF specification](https://theupdateframework.github.io/specification/v1.0.33/index.html), +//! however, it is not guaranteed that it does so fully. +//! +//! ## Why DER? +//! - DER is an encoding that is deterministic, +//! which is important for TUF as it requires signatures of *canonically* encoded files. +//! - DER parsing and encoding is more readily available than for canonical JSON, which is the default format for TUF, which does not have a `no-std` implementation. +//! - Additionally, DER is also required for certificate parsing and therefore potentially adds less overhead regarding dependencies. +//! - DER allows a great degree of parsing without requiring [ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) over the encoded data or copying it. Which is desirable for parsing on embedded devices. +//! +//! ## Example +//! +//! **Note:** this example use the sync functions, the same functions also exist as async functions. +//! +//! ```ignore +//! use tuf_no_std::utils::MemoryStorage; +//! use tuf_no_std::storage::TufStorage; +//! use tuf_no_std::remote::TufTransport; +//! use tuf_no_std::{update_repo, fetch_and_verify_target_file}; +//! use heapless::Vec; +//! const TUF_ROOT = include_bytes("path/to/root.der"); +//! +//! // initialize storage and transport +//! let mut storage = MemoryStorage { +//! root: Vec::<_, _>::from(TUF_ROOT), +//! uncommitted_root: None, +//! timestamp: None, +//! snapshot: None, +//! targets: None, +//! }; +//! +//! let mut transport = { panic!("add your own transport here")}; +//! +//! +//! let update_start = { panic!("create an timestamp of this moment here") }; +//! +//! update_repo( +//! &mut storage, +//! &mut transport, +//! 10, +//! update_start, +//! ).expect("update repo failed"); +//! +//! // fetching and verifying files using the updated repo +//! // allocate buffer to store the data +//! let mut buf = [0u8; 4096]; +//! let verified_file = fetch_and_verify_target_file( +//! &mut storage, +//! &mut transport, +//! b"hello-world.txt", +//! &mut buf, +//! ).expect("could not fetch and verify target file"); +//! ``` +//! ## Storage +//! +//! If you need a implementor of the [TufStorage] trait you can use [utils::MemoryStorage] or implement your own solution using the trait. +//! +//! ## TufTransport/Remote +//! +//! This crate does not provide an implementation for the [TufTransport] and [TufTransportAsync] traits. +//! You can find one for platforms that use the [`embassy-net`](https://docs.embassy.dev/embassy-net/git/default/index.html) crate in the `bt2x-ota-common` crate. +//! + +#[cfg(feature = "async")] +use common::remote::TufTransportAsync; +pub use der::asn1::UtcTime; +pub use der::DateTime; +use role::targets::verify_target_file; +pub use tuf_no_std_common::{remote::TufTransport, storage::TufStorage, TufError}; + +use crate::role::{ + root::update_root, snapshot::update_snapshot, targets::update_targets, + timestamp::update_timestamp, +}; +#[cfg(feature = "async")] +use crate::role::{ + snapshot::update_snapshot_async, targets::update_targets_async, + timestamp::update_timestamp_async, +}; +extern crate alloc; + +/// Module for builders to create TUF repositories. +pub mod builder; +/// Canonical encoding of metadata files. +pub mod canonical; +/// Abstract the concept of a file format that implements TUF. +pub mod format; +/// Implementation of TUF roles. +pub mod role; +/// Trait to abstract signatures. +mod signature; +/// Error types and more traits. +pub use tuf_no_std_common as common; +/// Utility functions. +pub mod utils; +/// Re-export of heapless for convenience. +pub use heapless; +/// Re-export of constants. +pub use tuf_no_std_common::constants; +/// Re-export of errors. +pub use tuf_no_std_common::error; +/// Fetching TUF files from remotes. +pub use tuf_no_std_common::remote; +/// Storage of TUF files. Only has traits, there is an in-memory implementation available in [utils::MemoryStorage]. +pub use tuf_no_std_common::storage; +pub enum TufFormat { + #[cfg(feature = "der")] + Der, +} + +/// Run a full TUF repo update. For more information refer to the [TUF specification](https://theupdateframework.github.io/specification/latest/). +/// This requires an initial root file in the `storage` object. The number specified in `max_fetches` limits the number of attempts to fetch new roots. +/// The value provided by `update_start` should be right before the update was started. +#[cfg(feature = "async")] +pub fn update_repo( + storage: &mut S, + transport: &mut T, + max_fetches: u32, + update_start: impl TryInto, +) -> Result<(), TufError> { + let update_start = &update_start + .try_into() + .map_err(|_| TufError::InvalidUtcTimestamp)?; + update_root(transport, storage, max_fetches, update_start)?; + update_timestamp(transport, storage, update_start)?; + update_snapshot(transport, storage, update_start)?; + update_targets(transport, storage, update_start)?; + Ok(()) +} + +/// Run a full TUF repo update. For more information refer to the [TUF specification](https://theupdateframework.github.io/specification/latest/). +/// This requires an initial root file in the `storage` object. The number specified in `max_fetches` limits the number of attempts to fetch new roots. +/// The value provided by `update_start` should be right before the update was started. +#[cfg(feature = "async")] +pub async fn update_repo_async( + storage: &mut S, + transport: &mut T, + max_fetches: u32, + update_start: impl TryInto, +) -> Result<(), TufError> { + use role::root::update_root_async; + + let update_start = &update_start + .try_into() + .map_err(|_| TufError::InvalidUtcTimestamp)?; + update_root_async(transport, storage, max_fetches, update_start).await?; + update_timestamp_async(transport, storage, update_start).await?; + update_snapshot_async(transport, storage, update_start).await?; + update_targets_async(transport, storage, update_start).await?; + Ok(()) +} + +/// Fetches and verifies the target file at the specified `metapath`. +/// This requires an updated TUF repo. +pub fn fetch_and_verify_target_file<'o, S: TufStorage, T: TufTransport>( + storage: &mut S, + transport: &mut T, + metapath: &[u8], + out: &'o mut [u8], +) -> Result<&'o [u8], TufError> { + let target_file = transport + .fetch_target_file(metapath, out) + .map_err(|_| TufError::FetchError)?; + let targets = storage + .current_targets() + .ok_or(TufError::MissingTargetsFile)?; + verify_target_file(targets, metapath, target_file)?; + Ok(target_file) +} + +/// Fetches and verifies the target file at the specified `metapath`. +/// This requires an updated TUF repo. +#[cfg(feature = "async")] +pub async fn fetch_and_verify_target_file_async<'o, S: TufStorage, T: TufTransportAsync>( + storage: &mut S, + transport: &mut T, + metapath: &[u8], + out: &'o mut [u8], +) -> Result<&'o [u8], TufError> { + let target_file = transport + .fetch_target_file(metapath, out) + .await + .map_err(|_| TufError::FetchError)?; + let targets = storage + .current_targets() + .ok_or(TufError::MissingTargetsFile)?; + verify_target_file(targets, metapath, target_file)?; + Ok(target_file) +} diff --git a/tuf-no-std/tuf-no-std/src/role/mod.rs b/tuf-no-std/tuf-no-std/src/role/mod.rs new file mode 100644 index 0000000..50e0cb7 --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/role/mod.rs @@ -0,0 +1,167 @@ +pub mod root; +pub mod snapshot; +pub mod targets; +pub mod timestamp; + +use tuf_no_std_common::{RoleType, TufError, Version}; + +use crate::canonical::{EncodeCanonically, EncodingError}; +use crate::role::targets::TufTargets; +use core::fmt::Debug; +use der::asn1::UtcTime; +use der::{Decode, Encode}; +use sha2::{Digest, Sha256}; +use tuf_no_std_der::{SignatureRef, SignedRef}; + +/// Trait for easy access to a constant that specifies the role. +pub trait TufRole { + const TYPE: RoleType; +} + +/// Trait to abstract the decoding of a TUF role. +pub trait DecodeRole<'a>: Sized { + fn decode_role(input: &'a [u8]) -> Result; +} + +/// Trait to extract information that is required to update a TUF role. +pub trait RoleUpdate: TufRole + EncodeCanonically { + fn version(&self) -> Version; + fn expires(&self) -> UtcTime; +} + +/// Trait to abstract signatures within TUF files. +pub trait TufSignature { + /// Extract the raw signature. + fn raw_sig(&self) -> &[u8]; + /// Return the ID of the key that was used to create the signature. + fn keyid(&self) -> [u8; 32]; +} + +/// Trait used to abstract signed files. +pub trait SignedFile { + type Signature: TufSignature; + type Signed: EncodeCanonically; + + /// Return the signatures of this file. + fn get_signatures(&self) -> serde_json_core::heapless::Vec<&Self::Signature, 16>; + /// Return the part of the file that is used to create signatures. + fn get_signed(&self) -> &Self::Signed; +} + +/// Trait used to abstract operations of a TUF timestamp file. +pub trait TufTimestamp { + /// Extract the version of the snapshot file specified in this timestamp file. + fn snapshot_version(&self) -> Version; + /// Extract the expiration date of the snapshot file specified in this timestamp file. + fn snapshot_expires(&self) -> UtcTime; + /// Extract the hash of the snapshot file specified in this timestamp file. + fn snapshot_hash(&self) -> [u8; 32]; +} + +/// Trait used to abstract operations of a TUF snapshot file. +pub trait TufSnapshot: EncodeCanonically { + /// Extract the hash of the targets file specified in this snapshot file. + fn targets_hash(&self) -> [u8; 32]; + /// Calculate the hash of this snapshot file. + fn snapshot_hash(&self) -> Result<[u8; 32], TufError> { + let mut buf = [0u8; 8096]; + self.encode_canonically(&mut buf) + .map(|encoded| Sha256::new().chain_update(encoded).finalize().into()) + .map_err(|_| TufError::EncodingError) + } + /// Extract the version of the targets file specified in this snapshot file. + fn targets_version(&self) -> Version; +} + +impl<'a, T> DecodeRole<'a> for T +where + T: Decode<'a> + TufRole, +{ + fn decode_role(input: &'a [u8]) -> Result { + Decode::from_der(input).map_err(|_| TufError::DecodingError) + } +} + +impl<'a, T> RoleUpdate for SignedRef<'a, T> +where + T: RoleUpdate + Debug + Eq + PartialEq + Decode<'a> + Encode, +{ + fn version(&self) -> Version { + self.signed.version() + } + fn expires(&self) -> UtcTime { + self.signed.expires() + } +} + +impl<'a, T> TufRole for SignedRef<'a, T> +where + T: TufRole + Debug + Eq + PartialEq + Decode<'a> + Encode, +{ + const TYPE: RoleType = T::TYPE; +} + +impl<'a, T> TufTimestamp for SignedRef<'a, T> +where + T: TufTimestamp + Debug + Eq + PartialEq + Decode<'a> + Encode, +{ + fn snapshot_version(&self) -> Version { + self.signed.snapshot_version() + } + + fn snapshot_expires(&self) -> UtcTime { + self.signed.snapshot_expires() + } + + fn snapshot_hash(&self) -> [u8; 32] { + self.signed.snapshot_hash() + } +} + +impl<'a, T> EncodeCanonically for SignedRef<'a, T> +where + T: Debug + Decode<'a> + Encode + Eq + PartialEq + RoleUpdate, +{ + fn encode_canonically<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], EncodingError> { + match self.signed.encode_to_slice(out) { + Ok(data) => Ok(data), + Err(err) => { + panic!("{err:?}") + } + } + } +} + +impl<'a, T> TufSnapshot for SignedRef<'a, T> +where + T: TufSnapshot + Debug + Eq + PartialEq + Decode<'a> + Encode + RoleUpdate, +{ + fn targets_hash(&self) -> [u8; 32] { + self.signed.targets_hash() + } + + fn targets_version(&self) -> Version { + self.signed.targets_version() + } +} + +impl<'a, T> TufTargets for SignedRef<'a, T> where + T: TufTargets + Debug + Eq + PartialEq + Decode<'a> + Encode +{ +} + +impl<'a, T> SignedFile for SignedRef<'a, T> +where + T: EncodeCanonically + Debug + Eq + PartialEq + Decode<'a> + Encode, +{ + type Signature = SignatureRef<'a>; + type Signed = T; + + fn get_signatures(&self) -> serde_json_core::heapless::Vec<&Self::Signature, 16> { + self.signatures.iter().collect() + } + + fn get_signed(&self) -> &Self::Signed { + &self.signed + } +} diff --git a/tuf-no-std/tuf-no-std/src/role/root.rs b/tuf-no-std/tuf-no-std/src/role/root.rs new file mode 100644 index 0000000..a047176 --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/role/root.rs @@ -0,0 +1,1050 @@ +use crate::canonical::EncodeCanonically; +use crate::role::{DecodeRole, RoleUpdate, SignedFile, TufRole, TufSignature}; +use core::cmp::max; +use core::num::NonZeroU64; +use core::ops::Add; +use der::asn1::UtcTime; +use der::{Decode, Encode}; +use serde_json_core::heapless::FnvIndexMap; +use spki::SubjectPublicKeyInfoRef; +use tuf_no_std_common::common::Threshold; +use tuf_no_std_common::remote::TufTransport; +#[cfg(feature = "async")] +use tuf_no_std_common::remote::TufTransportAsync; +use tuf_no_std_common::storage::TufStorage; +use tuf_no_std_common::TufError::*; +use tuf_no_std_common::{RoleType, TufError, Version}; +use tuf_no_std_der::root::{Root, RootRef}; +use tuf_no_std_der::{verify, SignedRef}; + +/// A trait that represents all the operations that can be done with a root file on the client side. +pub trait TufRoot: TufRole + SignedFile + EncodeCanonically { + type Key; + /// Verify that the role was signed by the root. + fn verify_role(&self, role: &T) -> Result<(), TufError> + where + T: TufRole + EncodeCanonically + SignedFile, + { + let mut buf = [0u8; 4096]; + let msg = role + .get_signed() + .encode_canonically(&mut buf) + .map_err(|_| TufError::EncodingError)?; + let threshold = self.role_threshold(T::TYPE); + + let mut role_keys = self.role_keys(T::TYPE).expect("failed to get role keys"); + let reached = role.get_signatures().iter().fold(0, |acc, sig| { + let raw_sig = sig.raw_sig(); + let key_id = sig.keyid(); + + // Using remove ensures each key is only used to create a single signature. + let Some(key) = role_keys.remove(&key_id) else { + return acc; + }; + Self::verify_signature(&key, msg, raw_sig) + .map(|_| 1) + .unwrap_or(0) + .add(acc) + }); + + if reached < threshold { + return Err(ThresholdNotReached); + } + Ok(()) + } + + /// Verify that the other root is signed by this root. + fn verify_root(&self, other: &Self) -> Result<(), TufError> { + let mut buf = [0u8; 4096]; + let msg = other + .get_signed() + .encode_canonically(&mut buf) + .map_err(|_| TufError::EncodingError)?; + let threshold = max( + self.role_threshold(RoleType::Root), + self.role_threshold(RoleType::Root), + ); + // Use the key specified in this file. + let role_keys = self + .role_keys(RoleType::Root) + .expect("failed to get role keys"); + // Signatures are specified in the other file! + let reached = other.get_signatures().iter().fold(0, |acc, sig| { + let raw_sig = sig.raw_sig(); + let key_id = sig.keyid(); + + let Some(key) = role_keys.get(&key_id) else { + return acc; + }; + Self::verify_signature(key, msg, raw_sig) + .map(|_| 1) + .unwrap_or(0) + .add(acc) + }); + + if reached < threshold { + return Err(ThresholdNotReached); + } + Ok(()) + } + + /// Extract the signing threshold for the given role. + fn role_threshold(&self, role: RoleType) -> Threshold; + + /// Return the keys for a given role. + fn role_keys(&self, role: RoleType) -> Option>; + + /// Verify the signature of the message with the role keys. + fn verify_signature(key: &Self::Key, msg: &[u8], sig: &[u8]) -> Result<(), TufError>; +} + +impl<'a> TufRole for RootRef<'a> { + const TYPE: RoleType = RoleType::Root; +} + +impl<'a> EncodeCanonically for RootRef<'a> { + fn encode_canonically<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], crate::canonical::EncodingError> { + self.encode_to_slice(out) + .map_err(|_| crate::canonical::EncodingError::BufferTooSmall) + } +} + +impl<'a> RoleUpdate for RootRef<'a> { + fn version(&self) -> Version { + self.version + } + + fn expires(&self) -> UtcTime { + self.expires + } +} + +impl<'a> TufRoot for SignedRef<'a, RootRef<'a>> { + type Key = SubjectPublicKeyInfoRef<'a>; + + fn role_threshold(&self, role: RoleType) -> Threshold { + self.signed + .roles + .iter() + .find(|r| role == r.name) + .map(|r| r.threshold) + .expect("did not find role") + } + + fn role_keys(&self, role: RoleType) -> Option> { + // Get the role. + let role = self.signed.roles.iter().find(|&r| role == r.name)?; + // Collect the matching keys. + let mut result = FnvIndexMap::new(); + for key_id in role.keyids.iter() { + let key_id = key_id + .as_bytes() + .expect("failed to convert to bytes") + .try_into() + .expect("failed to convert to sized array"); + let key = self + .signed + .keys + .iter() + .find(|key| key.fingerprint_bytes().unwrap().as_slice() == key_id) + .expect("failed to calculate fingerprint"); + result + .insert(key_id, key.clone()) + .expect("failed to insert key"); + } + Some(result) + } + + fn verify_signature(key: &Self::Key, msg: &[u8], sig: &[u8]) -> Result<(), TufError> { + verify(key, msg, sig).map_err(|_| TufError::InvalidSignature) + } +} + +impl TufRole for Root { + const TYPE: RoleType = RoleType::Root; +} + +/// Verify that the new root is valid given the old root. Returns the new root if successful. +pub(crate) fn update_root_step<'a, 'b>( + old_root: &SignedRef<'b, RootRef<'b>>, + new_root: &'a [u8], +) -> Result>, TufError> { + let next_root = SignedRef::::from_der(new_root).expect("failed to parse next root"); + // verify signatures + // FIXME: old_root does not know the signatures from the new root and therefore cannot verify it. + old_root.verify_root(&next_root)?; + next_root.verify_role(&next_root)?; + // check for a rollback attack (5.3.5) + if next_root.version() != old_root.version().saturating_add(1) && old_root != &next_root { + return Err(InvalidNewVersionNumber); + } + Ok(next_root) +} + +/// Refer to the [TUF specification section on updating the root role](https://theupdateframework.github.io/specification/latest/#update-root) for more information +pub fn update_root( + remote: &mut T, + storage: &mut S, + max_fetches: u32, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransport, +{ + let mut fetches_left = max_fetches; + //let mut new_root_buf = [0u8; 8096]; + + let mut buf = [0u8; 2048]; + let initial_root = storage.current_root_copy(&mut buf); + let initial_root_decoded = SignedRef::::decode_role(initial_root)?; + let next_version = NonZeroU64::new(initial_root_decoded.signed.version as u64 + 1) + .ok_or(TufError::InternalError)?; + loop { + if fetches_left == 0 { + break; + } + let mut buf = [0u8; 2048]; + let Ok(next_root) = remote.fetch_root(next_version, &mut buf) else { + break; + }; + fetches_left -= 1; + let current_root = storage + .current_uncommitted_root() + .and_then(|root| SignedRef::::decode_role(root).ok()) + .unwrap_or(initial_root_decoded.clone()); + update_root_step(¤t_root, next_root)?; + storage.persist_root(next_root)?; + } + + let Some(new_root) = storage.current_uncommitted_root() else { + // assume no new root + return Ok(()); + }; + let new_root = SignedRef::::decode_role(new_root)?; + if &new_root.expires() < update_start { + return Err(ExpiredRootFile); + } + // delete timestamp or snapshot metadata when keys are rotated (5.3.11) + let delete_timestamp = + if let Some(timestamp_keys_old) = initial_root_decoded.role_keys(RoleType::Timestamp) { + let timestamp_keys_new = new_root + .role_keys(RoleType::Timestamp) + .ok_or(MissingTimestampKeys)?; + + timestamp_keys_new + .into_iter() + .any(|(key_id, _)| !timestamp_keys_old.contains_key(&key_id)) + } else { + false + }; + let delete_snapshot = + if let Some(snapshot_keys_old) = initial_root_decoded.role_keys(RoleType::Snapshot) { + let snapshot_keys_new = new_root + .role_keys(RoleType::Snapshot) + .ok_or(MissingSnapshotKeys)?; + + snapshot_keys_new + .into_iter() + .any(|(key_id, _)| !snapshot_keys_old.contains_key(&key_id)) + } else { + false + }; + // this is handled here because of borrowing rules + if delete_timestamp { + storage.delete_timestamp_metadata(); + }; + if delete_snapshot { + storage.delete_snapshot_metadata(); + }; + + storage.commit_root() +} + +/// Refer to the [TUF specification section on updating the root role](https://theupdateframework.github.io/specification/latest/#update-root) for more information +#[cfg(feature = "async")] +pub async fn update_root_async( + remote: &mut T, + storage: &mut S, + max_fetches: u32, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransportAsync, +{ + let mut fetches_left = max_fetches; + //let mut new_root_buf = [0u8; 8096]; + + let mut buf = [0u8; 2048]; + let initial_root = storage.current_root_copy(&mut buf); + let initial_root_decoded = SignedRef::::decode_role(initial_root)?; + let next_version = NonZeroU64::new(initial_root_decoded.signed.version as u64 + 1) + .ok_or(TufError::InternalError)?; + loop { + if fetches_left == 0 { + break; + } + let mut buf = [0u8; 2048]; + let Ok(next_root) = remote.fetch_root(next_version, &mut buf).await else { + break; + }; + fetches_left -= 1; + let current_root = storage + .current_uncommitted_root() + .and_then(|root| SignedRef::::decode_role(root).ok()) + .unwrap_or(initial_root_decoded.clone()); + update_root_step(¤t_root, next_root)?; + storage.persist_root(next_root)?; + } + + let Some(new_root) = storage.current_uncommitted_root() else { + // assume no new root + return Ok(()); + }; + let new_root = SignedRef::::decode_role(new_root)?; + if &new_root.expires() < update_start { + return Err(ExpiredRootFile); + } + // delete timestamp or snapshot metadata when keys are rotated (5.3.11) + let delete_timestamp = + if let Some(timestamp_keys_old) = initial_root_decoded.role_keys(RoleType::Timestamp) { + let timestamp_keys_new = new_root + .role_keys(RoleType::Timestamp) + .ok_or(MissingTimestampKeys)?; + + timestamp_keys_new + .into_iter() + .any(|(key_id, _)| !timestamp_keys_old.contains_key(&key_id)) + } else { + false + }; + let delete_snapshot = + if let Some(snapshot_keys_old) = initial_root_decoded.role_keys(RoleType::Snapshot) { + let snapshot_keys_new = new_root + .role_keys(RoleType::Snapshot) + .ok_or(MissingSnapshotKeys)?; + + snapshot_keys_new + .into_iter() + .any(|(key_id, _)| !snapshot_keys_old.contains_key(&key_id)) + } else { + false + }; + // this is handled here because of borrowing rules + if delete_timestamp { + storage.delete_timestamp_metadata(); + }; + if delete_snapshot { + storage.delete_snapshot_metadata(); + }; + + storage.commit_root() +} + +#[cfg(test)] +mod test { + use crate::builder::RootBuilder; + use crate::utils::spki_from_signing_key; + use alloc::vec; + use der::asn1::BitString; + use der::{Decode, Encode}; + use ed25519_dalek::SigningKey; + use rand_core::OsRng; + use tuf_no_std_common::crypto::sign::RawSignature; + use tuf_no_std_der::{Signed, SignedRef}; + + use crate::role::root::{update_root_step, TufRoot}; + use der::referenced::OwnedToRef; + use ed25519::Signature; + use tuf_no_std_common::crypto::sign::SigningKey::Ed25519Dalek; + use tuf_no_std_der::root::RootRef; + + #[test] + fn test_root_update_step_valid() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = root_key_old.as_spki().unwrap(); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = root_key_new.as_spki().unwrap(); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + + let signed_new = Signed::from_signed(root_new, &[root_key_old, root_key_new]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new).expect("failed to verify"); + } + + /// Test if using the same root again is accepted. + #[test] + fn test_root_update_step_valid_self() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + update_root_step( + &signed_old.owned_to_ref(), + signed_old.to_der().unwrap().as_slice(), + ) + .expect("failed to verify"); + } + + #[test] + fn test_root_update_step_invalid_version_too_low() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_new = Signed::from_signed(root_new, &[root_key_old, root_key_new]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new) + .expect_err("this should not be accepted"); + } + + #[test] + fn test_root_update_step_threshold_not_reached() { + let mut csprng = OsRng; + + let root_key_old_1 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki_1 = spki_from_signing_key(&root_key_old_1); + + let root_key_old_2 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki_2 = spki_from_signing_key(&root_key_old_2); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki_1, root_pub_spki_2], 2) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = + Signed::from_signed(old_root, &[root_key_old_1.clone(), root_key_old_2.clone()]) + .unwrap(); + + let root_key_new_1 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki_1 = spki_from_signing_key(&root_key_new_1); + + let root_key_new_2 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki_2 = spki_from_signing_key(&root_key_new_2); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki_1, root_pub_spki_2], 2) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + + // Case: threshold not reached for new root + let signed_new = Signed::from_signed( + root_new.clone(), + &[ + root_key_old_1.clone(), + root_key_old_2, + root_key_new_1.clone(), + ], + ) + .unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new) + .expect_err("accepted root where new file does not meet threshold"); + + // Case: threshold not reached for old root + let signed_new = Signed::from_signed( + root_new.clone(), + &[ + root_key_old_1.clone(), + root_key_new_1.clone(), + root_key_new_2.clone(), + ], + ) + .unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new) + .expect_err("accepted root where new file does not meet threshold"); + + // Case: keys are reused + let signed_new = Signed::from_signed( + root_new, + &[ + root_key_old_1.clone(), + root_key_old_1.clone(), + root_key_new_1.clone(), + root_key_new_1.clone(), + ], + ) + .unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new) + .expect_err("accepted root where signing keys contributed multiple signatures"); + } + + #[test] + fn test_root_update_step_invald_version_too_high() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_new = Signed::from_signed(root_new, &[root_key_old, root_key_new]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new) + .expect_err("this should not be accepted"); + } + + /// Tests for failure when the new root is signed by a random key instead of the new key. + #[test] + fn test_root_update_step_invald_sig() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + + let root_key_random = Ed25519Dalek(SigningKey::generate(&mut csprng)); + + let signed_new = Signed::from_signed(root_new, &[root_key_old, root_key_random]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new).expect_err("expected failure"); + } + + #[test] + fn test_root_update_step_invald_duplicate_sig_old_root() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + + let signed_new = + Signed::from_signed(root_new, &[root_key_old.clone(), root_key_old.clone()]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new).expect_err("expected failure"); + } + + /// Tests if a root is accepted despite the previous root not having signed it. + #[test] + fn test_root_update_step_invalid_no_sig_old_root() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + + let signed_new = Signed::from_signed(root_new, &[root_key_new]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new).expect_err("expected failure"); + } + + /// Tests if a root is accepted despite the new root not having signed it. + #[test] + fn test_root_update_step_invalid_no_sig_new_root() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + + let signed_new = Signed::from_signed(root_new, &[root_key_old]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new).expect_err("expected failure"); + } + + /// Tests if a root is accepted despite no root not having signed it. + #[test] + fn test_root_update_step_invalid_no_sig() { + let mut csprng = OsRng; + + let root_key_old = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_old); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_old = Signed::from_signed(old_root, &[root_key_old.clone()]).unwrap(); + + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + + let signed_new = Signed::from_signed(root_new, &[]).unwrap(); + let signed_new = signed_new.to_der().unwrap(); + update_root_step(&signed_old.owned_to_ref(), &signed_new).expect_err("expected failure"); + } + + #[test] + fn test_root() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + let root_key_id = root_pub_spki.fingerprint_bytes().unwrap(); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let root = RootBuilder::default() + .consistent_snapshot(false) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki], 1) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .with_role_and_key("snapshot", &[snapshot_pub_spki], 1) + .build(); + + let encoded_root = root.to_der().unwrap(); + let RawSignature::Ed25519Dalek(signature) = root_key.sign(&encoded_root).unwrap() else { + unreachable!() + }; + let signature = tuf_no_std_der::Signature { + keyid: BitString::from_bytes(root_key_id.as_slice()).unwrap(), + sig: BitString::from_bytes(&signature.to_bytes()).unwrap(), + }; + let signed = Signed { + signed: root, + signatures: vec![signature], + _phantom: Default::default(), + }; + let decoded_root: RootRef = Decode::from_der(&encoded_root).unwrap(); + let Ed25519Dalek(root_key) = root_key else { + unreachable!() + }; + root_key + .verify( + &decoded_root.to_der().unwrap(), + &Signature::from_bytes( + signed.signatures[0] + .sig + .as_bytes() + .unwrap() + .try_into() + .unwrap(), + ), + ) + .unwrap(); + let signed = signed.owned_to_ref(); + let decoded_root = SignedRef { + signatures: signed.signatures.clone(), + signed: decoded_root, + }; + decoded_root.verify_role(&signed).unwrap(); + } + + #[cfg(test)] + mod test_update_root { + use crate::builder::RootBuilder; + use crate::utils::{MockStorage, MockTransport}; + use alloc::vec; + use core::num::NonZeroU64; + use der::asn1::UtcTime; + use der::{DateTime, Encode}; + use ed25519_dalek::SigningKey; + use rand_core::OsRng; + use tuf_no_std_common::crypto::sign::SigningKey::Ed25519Dalek; + use tuf_no_std_der::Signed; + + use crate::role::root::{update_root, update_root_async}; + + use super::spki_from_signing_key; + + #[test] + fn test_update_root() { + let mut csprng = OsRng; + + let old_root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + + let root_pub_spki = spki_from_signing_key(&old_root_key); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2023, 1, 1, 1, 1, 1) + .with_version(1) + .build(); + + let signing_keys = vec![old_root_key.clone()]; + let signed_old = Signed::from_signed(old_root, &signing_keys).unwrap(); + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2023, 1, 1, 1, 1, 1) + .with_version(2) + .build(); + + let signing_keys = vec![root_key_new, old_root_key]; + let signed_new = Signed::from_signed(root_new, &signing_keys).unwrap(); + //eprintln!("{signed_new:#?}"); + let signed_new = signed_new.to_der(); + let signed_new = signed_new.as_ref().unwrap().as_slice(); + let mut transport = MockTransport::<_, &[u8]> { + roots: heapless::FnvIndexMap::from_iter([ + (NonZeroU64::new(1).unwrap(), signed_old.to_der().unwrap()), + (NonZeroU64::new(2).unwrap(), signed_new.to_vec()), + ]), + timestamp: vec![], + snapshot: vec![], + targets: vec![], + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: signed_old.to_der().unwrap(), + uncommitted_root: None, + timestamp: None, + snapshot: None, + targets: None, + }; + update_root( + &mut transport, + &mut storage, + 1, + &UtcTime::from_date_time(DateTime::new(2023, 1, 1, 1, 1, 1).unwrap()).unwrap(), + ) + .expect("rejected correct update"); + let mut storage = MockStorage { + root: signed_old.to_der().unwrap(), + uncommitted_root: None, + timestamp: None, + snapshot: None, + targets: None, + }; + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + update_root_async( + &mut transport, + &mut storage, + 1, + &UtcTime::from_date_time(DateTime::new(2023, 1, 1, 1, 1, 1).unwrap()) + .unwrap(), + ) + .await + .expect("rejected correct update"); + }); + } + + #[test] + fn test_update_root_expired() { + let mut csprng = OsRng; + + let old_root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + + let root_pub_spki = spki_from_signing_key(&old_root_key); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2023, 1, 1, 1, 1, 1) + .with_version(1) + .build(); + + let signing_keys = vec![old_root_key.clone()]; + let signed_old = Signed::from_signed(old_root, &signing_keys).unwrap(); + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_expiration_utc(2000, 1, 1, 1, 1, 1) + .with_version(2) + .build(); + + let signing_keys = vec![root_key_new, old_root_key]; + let signed_new = Signed::from_signed(root_new, &signing_keys).unwrap(); + //eprintln!("{signed_new:#?}"); + let signed_new = signed_new.to_der(); + let signed_new = signed_new.as_ref().unwrap().as_slice(); + let mut transport = MockTransport::<_, &[u8]> { + roots: heapless::FnvIndexMap::from_iter([ + (NonZeroU64::new(1).unwrap(), signed_old.to_der().unwrap()), + (NonZeroU64::new(2).unwrap(), signed_new.to_vec()), + ]), + timestamp: vec![], + snapshot: vec![], + targets: vec![], + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: signed_old.to_der().unwrap(), + uncommitted_root: None, + timestamp: None, + snapshot: None, + targets: None, + }; + update_root( + &mut transport, + &mut storage, + 1, + &UtcTime::from_date_time(DateTime::new(2023, 1, 1, 1, 1, 1).unwrap()).unwrap(), + ) + .expect_err("accepted expired root"); + + let mut storage = MockStorage { + root: signed_old.to_der().unwrap(), + uncommitted_root: None, + timestamp: None, + snapshot: None, + targets: None, + }; + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + update_root_async( + &mut transport, + &mut storage, + 1, + &UtcTime::from_date_time(DateTime::new(2023, 1, 1, 1, 1, 1).unwrap()) + .unwrap(), + ) + .await + .expect_err("accepted expired root"); + }); + } + #[test] + fn test_update_root_role_key_rotation() { + let mut csprng = OsRng; + + let old_root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + + let root_pub_spki = spki_from_signing_key(&old_root_key); + let ts_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let ts_pub_spki = spki_from_signing_key(&ts_key); + let snap_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snap_pub_spki = spki_from_signing_key(&snap_key); + + let old_root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("snapshot", &[snap_pub_spki], 1) + .with_role_and_key("timestamp", &[ts_pub_spki], 1) + .with_expiration_utc(2023, 1, 1, 1, 1, 1) + .with_version(1) + .build(); + + let signing_keys = vec![old_root_key.clone()]; + let signed_old = Signed::from_signed(old_root, &signing_keys).unwrap(); + let root_key_new = Ed25519Dalek(SigningKey::generate(&mut csprng)); + + let root_pub_spki = spki_from_signing_key(&root_key_new); + + let ts_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let ts_pub_spki = spki_from_signing_key(&ts_key); + let snap_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snap_pub_spki = spki_from_signing_key(&snap_key); + + let root_new = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("snapshot", &[snap_pub_spki], 1) + .with_role_and_key("timestamp", &[ts_pub_spki], 1) + .with_expiration_utc(2023, 1, 1, 1, 1, 1) + .with_version(2) + .build(); + + let signing_keys = vec![root_key_new, old_root_key]; + let signed_new = Signed::from_signed(root_new, &signing_keys).unwrap(); + //eprintln!("{signed_new:#?}"); + let signed_new = signed_new.to_der(); + let signed_new = signed_new.as_ref().unwrap().as_slice(); + let mut transport = MockTransport::<_, &[u8]> { + roots: heapless::FnvIndexMap::from_iter([ + (NonZeroU64::new(1).unwrap(), signed_old.to_der().unwrap()), + (NonZeroU64::new(2).unwrap(), signed_new.to_vec()), + ]), + timestamp: vec![], + snapshot: vec![], + targets: vec![], + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: signed_old.to_der().unwrap(), + uncommitted_root: None, + timestamp: Some(Default::default()), + snapshot: Some(Default::default()), + targets: None, + }; + assert!( + storage.snapshot.is_some(), + "likely implementation error within the test" + ); + assert!( + storage.timestamp.is_some(), + "likely implementation error within the test" + ); + update_root( + &mut transport, + &mut storage, + 1, + &UtcTime::from_date_time(DateTime::new(2023, 1, 1, 1, 1, 1).unwrap()).unwrap(), + ) + .expect("rejected correct update"); + assert!(storage.snapshot.is_none(), "failed to delete snapshots"); + assert!(storage.timestamp.is_none(), "failed to delete timestamps"); + + let mut storage = MockStorage { + root: signed_old.to_der().unwrap(), + uncommitted_root: None, + timestamp: Some(Default::default()), + snapshot: Some(Default::default()), + targets: None, + }; + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + update_root_async( + &mut transport, + &mut storage, + 1, + &UtcTime::from_date_time(DateTime::new(2023, 1, 1, 1, 1, 1).unwrap()) + .unwrap(), + ) + .await + .expect("rejected correct update"); + }); + assert!(storage.snapshot.is_none(), "failed to delete snapshots"); + assert!(storage.timestamp.is_none(), "failed to delete timestamps"); + } + } +} diff --git a/tuf-no-std/tuf-no-std/src/role/snapshot.rs b/tuf-no-std/tuf-no-std/src/role/snapshot.rs new file mode 100644 index 0000000..4c9203d --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/role/snapshot.rs @@ -0,0 +1,843 @@ +use crate::canonical::{EncodeCanonically, EncodingError}; +use crate::role::root::TufRoot; +use crate::role::{DecodeRole, RoleUpdate, TufRole, TufSnapshot, TufTimestamp}; +use der::asn1::{BitStringRef, UtcTime}; +use der::Encode; +use tuf_no_std_common::crypto::verify_sha256; +use tuf_no_std_common::remote::TufTransport; +#[cfg(feature = "async")] +use tuf_no_std_common::remote::TufTransportAsync; +use tuf_no_std_common::storage::TufStorage; +use tuf_no_std_common::TufError::InvalidNewVersionNumber; +use tuf_no_std_common::{RoleType, TufError, Version}; +use tuf_no_std_der::root::RootRef; +use tuf_no_std_der::snapshot::{Snapshot, SnapshotRef}; +use tuf_no_std_der::timestamp::TimestampRef; +use tuf_no_std_der::SignedRef; +impl<'a> TufRole for SnapshotRef<'a> { + const TYPE: RoleType = RoleType::Snapshot; +} + +impl<'a> EncodeCanonically for SnapshotRef<'a> { + fn encode_canonically<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], EncodingError> { + self.encode_to_slice(out) + .map_err(|_| EncodingError::BufferTooSmall) + } +} + +impl EncodeCanonically for Snapshot { + fn encode_canonically<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], EncodingError> { + self.encode_to_slice(out) + .map_err(|_| EncodingError::BufferTooSmall) + } +} + +impl<'a> RoleUpdate for SnapshotRef<'a> { + fn version(&self) -> Version { + self.version + } + + fn expires(&self) -> UtcTime { + self.expires + } +} + +impl<'a> TufSnapshot for SnapshotRef<'a> { + fn targets_hash(&self) -> [u8; 32] { + self.meta + .iter() + .find(|f| f.metapath == BitStringRef::from_bytes(b"targets.der").unwrap()) + .and_then(|h| h.hashes.get(0)) + .and_then(|h| TryFrom::try_from(h.value.raw_bytes()).ok()) + .expect("failed to find hash") + } + + fn targets_version(&self) -> Version { + self.meta + .iter() + .find(|f| f.metapath == BitStringRef::from_bytes(b"targets.der").unwrap()) + .map(|h| h.version) + .expect("failed to find metapath") + } +} + +fn verify_snapshot( + root: &[u8], + timestamp: &[u8], + snapshot_new: &[u8], + snapshot_old: Option<&[u8]>, + update_start: &UtcTime, +) -> Result<(), TufError> { + let root = SignedRef::::decode_role(root)?; + let timestamp_decoded = SignedRef::::decode_role(timestamp)?; + let snapshot_new_decoded = SignedRef::::decode_role(snapshot_new)?; + + // 5.5.2 + verify_sha256(timestamp_decoded.snapshot_hash(), snapshot_new) + .map_err(|_| TufError::InvalidHash)?; + + // 5.5.3 + root.verify_role(&snapshot_new_decoded)?; + + // 5.5.4 + if timestamp_decoded.snapshot_version() != snapshot_new_decoded.version() { + return Err(InvalidNewVersionNumber); + } + // 5.5.5 Ensure every targets metadata file still exists + // and the old version numbers are less or equal to the old one. + if let Some(snapshot_old) = snapshot_old { + let snapshot_old = SignedRef::::decode_role(snapshot_old)?; + + snapshot_old + .signed + .meta + .iter() + .all(|t_old| { + snapshot_new_decoded + .signed + .meta + .iter() + .find(|t_new| t_new.metapath == t_old.metapath) + .map(|t_new| t_new.version >= t_old.version) + .unwrap_or(false) + }) + .then_some(()) + .ok_or(TufError::InvalidTargetsInSnapshot)?; + } + if timestamp_decoded.snapshot_version() != snapshot_new_decoded.version() { + return Err(TufError::InvalidNewVersionNumber); + } + + if &snapshot_new_decoded.expires() < update_start { + return Err(TufError::ExpiredSnapshotFile); + } + Ok(()) +} + +pub(crate) fn update_snapshot( + remote: &mut T, + storage: &mut S, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransport, +{ + let current_root = storage.current_root(); + let timestamp = storage + .current_timestamp() + .ok_or(TufError::MissingTimestampFile)?; + let snapshot_old = storage.current_snapshot(); + let mut buf = [0u8; 512]; + let snapshot_new = remote + .fetch_snapshot(&mut buf) + .map_err(|_| TufError::FetchError)?; + verify_snapshot( + current_root, + timestamp, + snapshot_new, + snapshot_old, + update_start, + )?; + storage.persist_snapshot(snapshot_new)?; + Ok(()) +} + +#[cfg(feature = "async")] +pub async fn update_snapshot_async( + remote: &mut T, + storage: &mut S, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransportAsync, +{ + let current_root = storage.current_root(); + let timestamp = storage + .current_timestamp() + .ok_or(TufError::MissingTimestampFile)?; + let snapshot_old = storage.current_snapshot(); + let mut buf = [0u8; 512]; + let snapshot_new = remote + .fetch_snapshot(&mut buf) + .await + .map_err(|_| TufError::FetchError)?; + verify_snapshot( + current_root, + timestamp, + snapshot_new, + snapshot_old, + update_start, + )?; + storage.persist_snapshot(snapshot_new)?; + Ok(()) +} + +#[cfg(test)] +mod test { + use core::num::NonZeroU64; + + use crate::builder::{RootBuilder, SnapshotBuilder, TargetsBuilder, TimestampBuilder}; + use crate::utils::{spki_from_signing_key, MockStorage, MockTransport}; + use der::asn1::BitString; + use der::{DateTime, Encode}; + use ed25519_dalek::SigningKey; + use rand_core::OsRng; + use tuf_no_std_common::crypto::sign::SigningKey::Ed25519Dalek; + use tuf_no_std_der::Signed; + + use super::{update_snapshot, verify_snapshot}; + + #[test] + fn test_verify_snapshot() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki], 1) + .with_role_and_key("snapshot", &[snapshot_pub_spki], 1) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = Signed::from_signed(targets, &[(targets_key.clone())]).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot) + .build(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + + // Case: valid + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + None, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("failed to verify valid snapshot file"); + + // Case: expired + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + None, + &DateTime::new(2040, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted expired snapshot"); + + // Case: mismatched hash + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + let mut timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot) + .build(); + + // modify hash + timestamp.meta.hashes[0].value = BitString::from_bytes(&[0u8; 32]).unwrap(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + None, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot with invalid hash"); + + // Case: valid step + let snapshot_old = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_old = + Signed::from_signed(snapshot_old.clone(), &[(snapshot_key.clone())]).unwrap(); + + let snapshot_new = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_new = + Signed::from_signed(snapshot_new.clone(), &[(snapshot_key.clone())]).unwrap(); + + let timestamp_new = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot_new) + .build(); + let signed_timestamp_new = + Signed::from_signed(timestamp_new, &[(timestamp_key.clone())]).unwrap(); + + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp_new.to_der().unwrap(), + &signed_snapshot_new.to_der().unwrap(), + Some(&signed_snapshot_old.to_der().unwrap()), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("rejected valid snapshot"); + + // Case: version number does not match timestamps file + let snapshot_new = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_new = + Signed::from_signed(snapshot_new.clone(), &[(snapshot_key.clone())]).unwrap(); + + let mut timestamp_new = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot_new) + .build(); + // Modify version number in timestamp file + timestamp_new.meta.version = 0; + let signed_timestamp_new = + Signed::from_signed(timestamp_new, &[(timestamp_key.clone())]).unwrap(); + + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp_new.to_der().unwrap(), + &signed_snapshot_new.to_der().unwrap(), + None, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot file with lower version number"); + } + + #[test] + fn test_verify_snapshot_threshold() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let snapshot_key_1 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki_1 = spki_from_signing_key(&snapshot_key_1); + let snapshot_key_2 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki_2 = spki_from_signing_key(&snapshot_key_2); + + let root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki], 1) + .with_role_and_key("snapshot", &[snapshot_pub_spki_1, snapshot_pub_spki_2], 2) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = Signed::from_signed(targets, &[(targets_key.clone())]).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = Signed::from_signed( + snapshot.clone(), + &[snapshot_key_1.clone(), snapshot_key_2.clone()], + ) + .unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot) + .build(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + + // Case: valid + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + None, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("failed to verify valid snapshot file"); + + // Case: signing keys are reused + let signed_snapshot = Signed::from_signed( + snapshot.clone(), + &[snapshot_key_2.clone(), snapshot_key_2.clone()], + ) + .unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot) + .build(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + + // Case: valid + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + None, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot file with reused keys"); + + // Case: untrusted keys are used + let signed_snapshot = Signed::from_signed( + snapshot.clone(), + &[ + Ed25519Dalek(SigningKey::generate(&mut csprng)), + Ed25519Dalek(SigningKey::generate(&mut csprng)), + ], + ) + .unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot) + .build(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + + // Case: valid + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + None, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot file with invalid signatures"); + } + + #[test] + fn test_verify_snapshot_targets_metadata() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki], 1) + .with_role_and_key("snapshot", &[snapshot_pub_spki], 1) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = Signed::from_signed(targets, &[(targets_key.clone())]).unwrap(); + + let snapshot_old = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_old = + Signed::from_signed(snapshot_old.clone(), &[(snapshot_key.clone())]).unwrap(); + + let snapshot_new = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 0) + .build(); + let signed_snapshot_new = + Signed::from_signed(snapshot_new.clone(), &[(snapshot_key.clone())]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot_new) + .build(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + + // Case: invalid version for target in new snapshot file + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot_new.to_der().unwrap(), + Some(&signed_snapshot_old.to_der().unwrap()), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot file with invalid targets version number"); + + let snapshot_old = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_old = + Signed::from_signed(snapshot_old.clone(), &[(snapshot_key.clone())]).unwrap(); + + let snapshot_new = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .build(); + let signed_snapshot_new = + Signed::from_signed(snapshot_new.clone(), &[(snapshot_key.clone())]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot_new) + .build(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + + // Case: invalid version for target in new snapshot file + verify_snapshot( + &signed_root.to_der().unwrap(), + &signed_timestamp.to_der().unwrap(), + &signed_snapshot_new.to_der().unwrap(), + Some(&signed_snapshot_old.to_der().unwrap()), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot file with missing targets metadata"); + } + + #[test] + fn test_update_snapshot() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki], 1) + .with_role_and_key("snapshot", &[snapshot_pub_spki], 1) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + let root_encoded = signed_root.to_der().unwrap(); + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = Signed::from_signed(targets, &[(targets_key.clone())]).unwrap(); + let targets_encoded = signed_targets.to_der().unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + let snapshot_encoded = signed_snapshot.to_der().unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot) + .build(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + let timestamp_encoded = signed_timestamp.to_der().unwrap(); + + let mut transport = MockTransport::<_, &[u8]> { + roots: heapless::FnvIndexMap::from_iter([( + NonZeroU64::new(1).unwrap(), + root_encoded.clone(), + )]), + timestamp: timestamp_encoded.clone(), + snapshot: snapshot_encoded.clone(), + targets: snapshot_encoded.clone(), + target_files: Default::default(), + }; + let storage = MockStorage { + root: root_encoded.clone(), + uncommitted_root: None, + timestamp: Some(timestamp_encoded.clone()), + snapshot: None, + targets: Some(targets_encoded.clone()), + }; + + // Case: valid + let mut storage_test = storage.clone(); + update_snapshot( + &mut transport, + &mut storage_test, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("failed to verify snapshot file"); + assert_eq!(storage_test.root, root_encoded); + assert_eq!(storage_test.timestamp, Some(timestamp_encoded.clone())); + assert_eq!(storage_test.snapshot, Some(snapshot_encoded.clone())); + assert_eq!(storage_test.targets, Some(targets_encoded.clone())); + + // Case: expired + let mut storage_test = storage.clone(); + update_snapshot( + &mut transport, + &mut storage_test, + &DateTime::new(2040, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted expired snapshot file"); + assert_eq!(storage_test.root, root_encoded); + assert_eq!(storage_test.timestamp, Some(timestamp_encoded.clone())); + assert_eq!(storage_test.snapshot, None); + assert_eq!(storage_test.targets, Some(targets_encoded.clone())); + + // Case: mismatched hash + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + let mut timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot) + .build(); + + // modify hash + timestamp.meta.hashes[0].value = BitString::from_bytes(&[0u8; 32]).unwrap(); + let signed_timestamp = Signed::from_signed(timestamp, &[(timestamp_key.clone())]).unwrap(); + let timestamp_encoded = signed_timestamp.to_der().unwrap(); + + let mut storage_test = storage.clone(); + storage_test.timestamp = Some(timestamp_encoded.clone()); + + update_snapshot( + &mut transport, + &mut storage_test, + &DateTime::new(2040, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot with invalid hash"); + assert_eq!(storage_test.root, root_encoded); + assert_eq!(storage_test.timestamp, Some(timestamp_encoded.clone())); + assert_eq!(storage_test.snapshot, None); + assert_eq!(storage_test.targets, Some(targets_encoded.clone())); + + // Case: valid step + let snapshot_old = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_old = + Signed::from_signed(snapshot_old.clone(), &[(snapshot_key.clone())]).unwrap(); + + let snapshot_new = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(2) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_new = + Signed::from_signed(snapshot_new.clone(), &[(snapshot_key.clone())]).unwrap(); + + let timestamp_new = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot_new) + .build(); + let signed_timestamp = + Signed::from_signed(timestamp_new, &[(timestamp_key.clone())]).unwrap(); + + let timestamp_encoded = signed_timestamp.to_der().unwrap(); + let snapshot_encoded_old = signed_snapshot_old.to_der().unwrap(); + let snapshot_encoded_new = signed_snapshot_new.to_der().unwrap(); + + let mut transport = MockTransport::<_, &[u8]> { + roots: heapless::FnvIndexMap::from_iter([( + NonZeroU64::new(1).unwrap(), + root_encoded.clone(), + )]), + timestamp: timestamp_encoded.clone(), + snapshot: snapshot_encoded_new.clone(), + targets: snapshot_encoded.clone(), + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: root_encoded.clone(), + uncommitted_root: None, + timestamp: Some(timestamp_encoded.clone()), + snapshot: Some(snapshot_encoded_old.clone()), + targets: Some(targets_encoded.clone()), + }; + update_snapshot( + &mut transport, + &mut storage, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("rejected valid snapshot"); + assert_eq!(storage.root, root_encoded); + assert_eq!(storage.timestamp, Some(timestamp_encoded.clone())); + assert_eq!(storage.snapshot, Some(snapshot_encoded_new.clone())); + assert_eq!(storage.targets, Some(targets_encoded.clone())); + + // Case: version number does not match timestamps file + let snapshot_new = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot_new = + Signed::from_signed(snapshot_new.clone(), &[(snapshot_key.clone())]).unwrap(); + + let mut timestamp_new = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_snapshot("snapshot.der", &signed_snapshot_new) + .build(); + // Modify version number in timestamp file + timestamp_new.meta.version = 0; + let signed_timestamp = + Signed::from_signed(timestamp_new, &[(timestamp_key.clone())]).unwrap(); + + let timestamp_encoded = signed_timestamp.to_der().unwrap(); + let snapshot_encoded_new = signed_snapshot_new.to_der().unwrap(); + + let mut transport = MockTransport::<_, &[u8]> { + roots: heapless::FnvIndexMap::from_iter([( + NonZeroU64::new(1).unwrap(), + root_encoded.clone(), + )]), + timestamp: timestamp_encoded.clone(), + snapshot: snapshot_encoded_new.clone(), + targets: snapshot_encoded.clone(), + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: root_encoded.clone(), + uncommitted_root: None, + timestamp: Some(timestamp_encoded.clone()), + snapshot: Some(snapshot_encoded_old.clone()), + targets: Some(targets_encoded.clone()), + }; + update_snapshot( + &mut transport, + &mut storage, + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted snapshot file with lower version number"); + assert_eq!(storage.root, root_encoded); + assert_eq!(storage.timestamp, Some(timestamp_encoded.clone())); + assert_eq!(storage.snapshot, Some(snapshot_encoded_old.clone())); + assert_eq!(storage.targets, Some(targets_encoded.clone())); + } +} diff --git a/tuf-no-std/tuf-no-std/src/role/targets.rs b/tuf-no-std/tuf-no-std/src/role/targets.rs new file mode 100644 index 0000000..bca9f3b --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/role/targets.rs @@ -0,0 +1,423 @@ +use crate::canonical::{EncodeCanonically, EncodingError}; +use crate::format::TufFormat; +use crate::role::root::TufRoot; +use crate::role::{DecodeRole, RoleUpdate, TufRole, TufSnapshot}; +use der::asn1::{BitStringRef, UtcTime}; +use der::Encode; +use tuf_no_std_common::crypto::verify_sha256; +use tuf_no_std_common::remote::TufTransport; +#[cfg(feature = "async")] +use tuf_no_std_common::remote::TufTransportAsync; +use tuf_no_std_common::storage::TufStorage; +use tuf_no_std_common::TufError::{ExpiredTargetsFile, InvalidNewVersionNumber}; +use tuf_no_std_common::{RoleType, TufError, Version}; +use tuf_no_std_der::targets::{Targets, TargetsRef}; +use tuf_no_std_der::{HashRef, TufDer}; + +pub trait TufTargets {} + +impl<'a> TufRole for TargetsRef<'a, 4, 0> { + const TYPE: RoleType = RoleType::Targets; +} + +impl<'a> EncodeCanonically for TargetsRef<'a, 4, 0> { + fn encode_canonically<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], EncodingError> { + self.encode_to_slice(out) + .map_err(|_| EncodingError::BufferTooSmall) + } +} + +impl EncodeCanonically for Targets { + fn encode_canonically<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], EncodingError> { + self.encode_to_slice(out) + .map_err(|_| EncodingError::BufferTooSmall) + } +} + +impl<'a> RoleUpdate for TargetsRef<'a, 4, 0> { + fn version(&self) -> Version { + self.version + } + + fn expires(&self) -> UtcTime { + self.expires + } +} + +impl<'a> TufTargets for TargetsRef<'a, 4, 0> {} + +pub(crate) fn verify_targets<'a>( + root: &'a [u8], + snapshot: &'a [u8], + targets_new: &'a [u8], + update_start: &UtcTime, +) -> Result<(), TufError> { + let root_decoded = ::Root::decode_role(root)?; + let snapshot_decoded = ::Snapshot::decode_role(snapshot)?; + let targets_new_decoded = ::Targets::decode_role(targets_new)?; + + verify_sha256(snapshot_decoded.targets_hash(), targets_new) + .map_err(|_| TufError::InvalidHash)?; + root_decoded.verify_role(&targets_new_decoded)?; + if targets_new_decoded.version() != snapshot_decoded.targets_version() { + return Err(InvalidNewVersionNumber); + } + if &targets_new_decoded.expires() < update_start { + return Err(ExpiredTargetsFile); + } + Ok(()) +} + +pub(crate) fn update_targets( + remote: &mut T, + storage: &mut S, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransport, +{ + let current_root = storage.current_root(); + let snapshot = storage + .current_snapshot() + .ok_or(TufError::MissingSnapshotFile)?; + + let mut buf = [0u8; 512]; + let targets_new = remote + .fetch_targets(&mut buf) + .map_err(|_| TufError::FetchError)?; + verify_targets(current_root, snapshot, targets_new, update_start)?; + storage.persist_targets(targets_new)?; + Ok(()) +} + +#[cfg(feature = "async")] +pub(crate) async fn update_targets_async( + remote: &mut T, + storage: &mut S, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransportAsync, +{ + let current_root = storage.current_root(); + let snapshot = storage + .current_snapshot() + .ok_or(TufError::MissingSnapshotFile)?; + + let mut buf = [0u8; 512]; + let targets_new = remote + .fetch_targets(&mut buf) + .await + .map_err(|_| TufError::FetchError)?; + verify_targets(current_root, snapshot, targets_new, update_start)?; + storage.persist_targets(targets_new)?; + Ok(()) +} + +pub(crate) fn verify_target_file( + targets: &[u8], + target_file_metapath: &[u8], + target_file: &[u8], +) -> Result<(), TufError> { + // 5.7.1 Verify the desired target against its targets metadata. + + let targets_decoded = ::Targets::decode_role(targets)?; + let target_file_metapath = + BitStringRef::from_bytes(target_file_metapath).or(Err(TufError::InternalError))?; + let metadata = &targets_decoded + .signed + .targets + .iter() + .find(|t| t.metapath == target_file_metapath) + .ok_or(TufError::MissingTargetMetadata)? + .value; + if metadata + .length + .map(|l| l != target_file.len() as u64) + .unwrap_or(false) + { + return Err(TufError::InvalidLength); + } + let expected_hash = metadata + .hashes + .iter() + .find_map(HashRef::sha256) + .ok_or(TufError::NoSupportedHash)?; + verify_sha256(expected_hash, target_file).map_err(|_| TufError::InvalidHash)?; + Ok(()) +} + +#[cfg(test)] +mod test { + + use der::asn1::BitString; + use der::{DateTime, Encode}; + use ed25519_dalek::SigningKey; + use rand_core::OsRng; + use tuf_no_std_common::crypto::sign::SigningKey::Ed25519Dalek; + use tuf_no_std_der::Signed; + + use crate::builder::{RootBuilder, SnapshotBuilder, TargetsBuilder}; + use crate::utils::spki_from_signing_key; + + use super::{verify_target_file, verify_targets}; + + #[test] + fn test_verify_targets() { + let target_file = "Hello World!"; + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki], 1) + .with_role_and_key("snapshot", &[snapshot_pub_spki], 1) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_target(b"hello-world.txt", target_file.as_bytes()) + .build(); + let signed_targets = Signed::from_signed(targets, &[(targets_key.clone())]).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + // Case: valid file + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("failed to verify valid snapshot file"); + + // Case: expired + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2040, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("failed to reject expired snapshot file"); + verify_target_file( + &signed_targets.to_der().unwrap(), + b"hello-world.txt", + target_file.as_bytes(), + ) + .expect("failed to verify targets file"); + + let mut snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + snapshot.meta[0].hashes[0].value = BitString::from_bytes(&[0u8; 32]).unwrap(); + + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted invalid targets file"); + + // Case: version number does not match snapshot + let mut snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + snapshot.meta[0].version = 0; + + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted invalid targets file"); + } + + #[test] + fn test_verify_targets_threshold() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let targets_key_1 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki_1 = spki_from_signing_key(&targets_key_1); + let targets_key_2 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki_2 = spki_from_signing_key(&targets_key_2); + + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let root = RootBuilder::default() + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki_1, targets_pub_spki_2], 2) + .with_role_and_key("snapshot", &[snapshot_pub_spki], 1) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + + // Case: valid, meets threshold + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = + Signed::from_signed(targets, &[targets_key_1.clone(), targets_key_2.clone()]).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("failed to verify valid snapshot file"); + + // Case: Does not meet threshold. + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = Signed::from_signed(targets, &[targets_key_1.clone()]).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted targets file that does not have enough signatures"); + + // Case: Does not meet threshold, keys are reused. + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = + Signed::from_signed(targets, &[targets_key_1.clone(), targets_key_1.clone()]).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted targets file that reused keys to meet threshold"); + + // Case: signatures from keys not specified for the role. + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + let signed_targets = Signed::from_signed( + targets, + &[ + Ed25519Dalek(SigningKey::generate(&mut csprng)), + Ed25519Dalek(SigningKey::generate(&mut csprng)), + ], + ) + .unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .with_meta(b"targets.der", &signed_targets.to_der().unwrap(), 1) + .build(); + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[(snapshot_key.clone())]).unwrap(); + + verify_targets( + &signed_root.to_der().unwrap(), + &signed_snapshot.to_der().unwrap(), + &signed_targets.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted targets file that used untrusted keys"); + } +} diff --git a/tuf-no-std/tuf-no-std/src/role/timestamp.rs b/tuf-no-std/tuf-no-std/src/role/timestamp.rs new file mode 100644 index 0000000..370df97 --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/role/timestamp.rs @@ -0,0 +1,544 @@ +use crate::canonical::{EncodeCanonically, EncodingError}; +use crate::role::root::TufRoot; +use crate::role::{DecodeRole, RoleUpdate, TufRole, TufTimestamp}; +use der::asn1::UtcTime; +use der::Encode; +use tuf_no_std_common::remote::TufTransport; +#[cfg(feature = "async")] +use tuf_no_std_common::remote::TufTransportAsync; +use tuf_no_std_common::storage::TufStorage; +use tuf_no_std_common::TufError::{ExpiredTimestampFile, InvalidNewVersionNumber}; +use tuf_no_std_common::{RoleType, TufError, Version}; +use tuf_no_std_der::root::RootRef; +use tuf_no_std_der::timestamp::TimestampRef; +use tuf_no_std_der::SignedRef; + +impl<'a> TufRole for TimestampRef<'a> { + const TYPE: RoleType = RoleType::Timestamp; +} + +impl<'a> EncodeCanonically for TimestampRef<'a> { + fn encode_canonically<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], EncodingError> { + self.encode_to_slice(out) + .map_err(|_| EncodingError::BufferTooSmall) + } +} + +impl<'a> RoleUpdate for TimestampRef<'a> { + fn version(&self) -> Version { + self.version + } + + fn expires(&self) -> UtcTime { + self.expires + } +} + +impl<'a> TufTimestamp for TimestampRef<'a> { + fn snapshot_version(&self) -> Version { + self.meta.version + } + + fn snapshot_expires(&self) -> UtcTime { + self.expires + } + + fn snapshot_hash(&self) -> [u8; 32] { + self.meta + .hashes + .get(0) + .expect("expected at least one hash") + .value + .as_bytes() + .and_then(|h| TryInto::try_into(h).ok()) + .unwrap() + } +} + +pub(crate) fn verify_timestamp<'a>( + root: &[u8], + timestamp_old: Option<&'a [u8]>, + timestamp_new: &'a [u8], + update_start: &UtcTime, +) -> Result<&'a [u8], TufError> { + let root = SignedRef::::decode_role(root)?; + let timestamp_new_decoded = SignedRef::::decode_role(timestamp_new)?; + + root.verify_role(×tamp_new_decoded)?; + + if let Some(timestamp_old) = timestamp_old { + let timestamp_old_decoded = SignedRef::::decode_role(timestamp_old)?; + if timestamp_new_decoded.version() < timestamp_old_decoded.version() { + return Err(InvalidNewVersionNumber); + } + if timestamp_new_decoded.snapshot_version() < timestamp_old_decoded.snapshot_version() { + return Err(InvalidNewVersionNumber); + } + // From the TUF spec: 'In case [the version numbers] are equal, + // discard the new timestamp metadata and abort the update cycle. + // This is normal and it shouldn’t raise any error.' + if timestamp_new_decoded.version() == timestamp_old_decoded.version() { + return Ok(timestamp_old); + } + } + + if ×tamp_new_decoded.expires() < update_start { + return Err(ExpiredTimestampFile); + } + Ok(timestamp_new) +} + +pub(crate) fn update_timestamp( + remote: &mut T, + storage: &mut S, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransport, +{ + let current_root = storage.current_root(); + let mut buf = [0u8; 512]; + let timestamp_old = storage.current_timestamp_copy(&mut buf); + let mut buf = [0u8; 512]; + let timestamp = remote + .fetch_timestamp(&mut buf) + .map_err(|_| TufError::FetchError)?; + let timestamp = verify_timestamp(current_root, timestamp_old, timestamp, update_start)?; + storage.persist_timestamp(timestamp)?; + Ok(()) +} + +#[cfg(feature = "async")] +pub(crate) async fn update_timestamp_async( + remote: &mut T, + storage: &mut S, + update_start: &UtcTime, +) -> Result<(), TufError> +where + S: TufStorage, + T: TufTransportAsync, +{ + let current_root = storage.current_root(); + let mut buf = [0u8; 512]; + let timestamp_old = storage.current_timestamp_copy(&mut buf); + let mut buf = [0u8; 512]; + let timestamp_new = remote + .fetch_timestamp(&mut buf) + .await + .map_err(|_| TufError::FetchError)?; + let timestamp = verify_timestamp(current_root, timestamp_old, timestamp_new, update_start)?; + storage.persist_timestamp(timestamp)?; + Ok(()) +} + +#[cfg(test)] +mod test { + use crate::builder::{RootBuilder, SnapshotBuilder, TargetsBuilder, TimestampBuilder}; + use crate::role::timestamp::verify_timestamp; + use crate::utils::{build_role, spki_from_signing_key, MockStorage, MockTransport}; + use alloc::vec; + use anyhow::anyhow; + use der::asn1::{BitString, UtcTime}; + use der::referenced::OwnedToRef; + use der::{DateTime, Encode}; + use ed25519_dalek::SigningKey; + use rand_core::OsRng; + use tuf_no_std_common::crypto::sign::SigningKey::Ed25519Dalek; + use tuf_no_std_der::root::Root; + use tuf_no_std_der::Signed; + + use super::update_timestamp; + + #[test] + fn test_verify_timestamp() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let root = RootBuilder::default() + .with_version(1) + .with_expiration_utc(2023, 1, 1, 1, 0, 0) + .with_role_and_key("root", &[root_pub_spki], 1) + .with_role_and_key("snapshot", &[snapshot_pub_spki], 1) + .with_role_and_key("targets", &[targets_pub_spki], 1) + .with_role_and_key("timestamp", &[timestamp_pub_spki], 1) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_targets = Signed::from_signed(targets, &[targets_key.clone()]).unwrap(); + + let mut buf = [0u8; 4096]; + let encoded_targets = signed_targets.encode_to_slice(&mut buf).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_meta(b"targets.der", encoded_targets, 1) + .with_version(1) + .build(); + + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[snapshot_key.clone()]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(1) + .build(); + + let signed_timestamp = Signed::from_signed(timestamp, &[timestamp_key]).unwrap(); + + verify_timestamp( + &signed_root.to_der().unwrap(), + None, + &signed_timestamp.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect("failed to verify"); + verify_timestamp( + &signed_root.to_der().unwrap(), + None, + &signed_timestamp.to_der().unwrap(), + &DateTime::new(2031, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted expired timestamp"); + } + + #[test] + fn test_verify_timestamp_threshold_not_reached() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key_1 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki_1 = spki_from_signing_key(×tamp_key_1); + let timestamp_key_2 = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki_2 = spki_from_signing_key(×tamp_key_2); + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let mut keys = vec![ + root_pub_spki.clone(), + targets_pub_spki.clone(), + timestamp_pub_spki_1.clone(), + timestamp_pub_spki_2.clone(), + snapshot_pub_spki.clone(), + ]; + + keys.sort_by_key(|k| k.owned_to_ref().fingerprint_bytes().unwrap()); + let root = Root { + consistent_snapshot: true, + expires: DateTime::new(2030, 1, 1, 0, 0, 0) + .map_err(|err| anyhow!("failed to create DateTime {err:?}")) + .and_then(|d| { + UtcTime::from_date_time(d) + .map_err(|err| anyhow!("failed to create UtcTime {err:?}")) + }) + .expect("could not create date"), + keys, + roles: vec![ + build_role("root", &[root_pub_spki], 1), + build_role("snapshot", &[snapshot_pub_spki], 1), + build_role("targets", &[targets_pub_spki], 1), + build_role( + "timestamp", + &[timestamp_pub_spki_1, timestamp_pub_spki_2], + 2, + ), + ], + spec_version: BitString::from_bytes(b"1.0").unwrap(), + version: 1, + }; + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_targets = Signed::from_signed(targets, &[targets_key.clone()]).unwrap(); + + let mut buf = [0u8; 4096]; + let encoded_targets = signed_targets.encode_to_slice(&mut buf).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_meta(b"targets.der", encoded_targets, 1) + .with_version(1) + .build(); + + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[snapshot_key.clone()]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(1) + .build(); + + let signed_timestamp = Signed::from_signed( + timestamp.clone(), + &[ + Ed25519Dalek(SigningKey::generate(&mut csprng)), + Ed25519Dalek(SigningKey::generate(&mut csprng)), + ], + ) + .unwrap(); + verify_timestamp( + &signed_root.to_der().unwrap(), + None, + &signed_timestamp.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted timestamp signed by unknown keys"); + + let signed_timestamp = Signed::from_signed(timestamp.clone(), &[]).unwrap(); + verify_timestamp( + &signed_root.to_der().unwrap(), + None, + &signed_timestamp.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted timestamp without any signatures"); + + let signed_timestamp = + Signed::from_signed(timestamp.clone(), &[timestamp_key_1.clone()]).unwrap(); + verify_timestamp( + &signed_root.to_der().unwrap(), + None, + &signed_timestamp.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted timestamp without meeting threshold"); + + let signed_timestamp = Signed::from_signed( + timestamp, + &[timestamp_key_1.clone(), timestamp_key_1.clone()], + ) + .unwrap(); + + verify_timestamp( + &signed_root.to_der().unwrap(), + None, + &signed_timestamp.to_der().unwrap(), + &DateTime::new(2023, 1, 1, 0, 0, 0) + .unwrap() + .try_into() + .unwrap(), + ) + .expect_err("accepted timestamp with duplicate signatures threshold"); + } + + #[test] + fn test_update_timestamp_version_number() { + let mut csprng = OsRng; + + let root_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let root_pub_spki = spki_from_signing_key(&root_key); + + let timestamp_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let timestamp_pub_spki = spki_from_signing_key(×tamp_key); + let snapshot_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let snapshot_pub_spki = spki_from_signing_key(&snapshot_key); + + let targets_key = Ed25519Dalek(SigningKey::generate(&mut csprng)); + let targets_pub_spki = spki_from_signing_key(&targets_key); + + let mut keys = vec![ + root_pub_spki.clone(), + targets_pub_spki.clone(), + timestamp_pub_spki.clone(), + snapshot_pub_spki.clone(), + ]; + + keys.sort_by_key(|k| k.owned_to_ref().fingerprint_bytes().unwrap()); + let root = Root { + consistent_snapshot: true, + expires: DateTime::new(2030, 1, 1, 0, 0, 0) + .map_err(|err| anyhow!("failed to create DateTime {err:?}")) + .and_then(|d| { + UtcTime::from_date_time(d) + .map_err(|err| anyhow!("failed to create UtcTime {err:?}")) + }) + .expect("could not create date"), + keys, + roles: vec![ + build_role("root", &[root_pub_spki], 1), + build_role("snapshot", &[snapshot_pub_spki], 1), + build_role("targets", &[targets_pub_spki], 1), + build_role("timestamp", &[timestamp_pub_spki], 1), + ], + spec_version: BitString::from_bytes(b"1.0").unwrap(), + version: 1, + }; + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_version(1) + .build(); + + let signed_targets = Signed::from_signed(targets, &[targets_key.clone()]).unwrap(); + + let mut buf = [0u8; 4096]; + let encoded_targets = signed_targets.encode_to_slice(&mut buf).unwrap(); + + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_meta(b"targets.der", encoded_targets, 1) + .with_version(1) + .build(); + + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[snapshot_key.clone()]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(1) + .build(); + + let signed_timestamp_old = + Signed::from_signed(timestamp.clone(), &[timestamp_key.clone()]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2031, 1, 1, 0, 0, 0) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(1) + .build(); + + let signed_timestamp_new = + Signed::from_signed(timestamp.clone(), &[timestamp_key.clone()]).unwrap(); + let mut transport = MockTransport::<_, &[u8]> { + roots: Default::default(), + timestamp: signed_timestamp_new.to_der().unwrap(), + snapshot: vec![], + targets: vec![], + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: signed_root.to_der().unwrap(), + uncommitted_root: None, + timestamp: Some(signed_timestamp_old.to_der().unwrap()), + snapshot: Some(signed_snapshot.to_der().unwrap()), + targets: Some(signed_targets.to_der().unwrap()), + }; + update_timestamp( + &mut transport, + &mut storage, + &UtcTime::from_date_time(DateTime::new(2030, 1, 1, 0, 0, 0).unwrap()).unwrap(), + ) + .expect("this should succeed"); + assert_eq!( + signed_timestamp_old.to_der().unwrap(), + storage.timestamp.unwrap(), + "expected no update to the timestamp" + ); + + // Case: version number is lower than the previous one + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2031, 1, 1, 0, 0, 0) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(0) + .build(); + + let signed_timestamp_new = + Signed::from_signed(timestamp.clone(), &[timestamp_key.clone()]).unwrap(); + let mut transport = MockTransport::<_, &[u8]> { + roots: Default::default(), + timestamp: signed_timestamp_new.to_der().unwrap(), + snapshot: vec![], + targets: vec![], + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: signed_root.to_der().unwrap(), + uncommitted_root: None, + timestamp: Some(signed_timestamp_old.to_der().unwrap()), + snapshot: Some(signed_snapshot.to_der().unwrap()), + targets: Some(signed_targets.to_der().unwrap()), + }; + update_timestamp( + &mut transport, + &mut storage, + &UtcTime::from_date_time(DateTime::new(2030, 1, 1, 0, 0, 0).unwrap()).unwrap(), + ) + .expect_err("this should fail"); + + // Case: version number of the snapshot metadata file in the trusted timestamp metadata file + // not less or equal to the one in the new timestamp. + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 0, 0, 0) + .with_meta(b"targets.der", encoded_targets, 1) + .with_version(0) + .build(); + + let signed_snapshot = + Signed::from_signed(snapshot.clone(), &[snapshot_key.clone()]).unwrap(); + + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2031, 1, 1, 0, 0, 0) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(2) + .build(); + + let signed_timestamp_new = + Signed::from_signed(timestamp.clone(), &[timestamp_key.clone()]).unwrap(); + assert_eq!(signed_timestamp_new.signed.meta.version, 0); + let mut transport = MockTransport::<_, &[u8]> { + roots: Default::default(), + timestamp: signed_timestamp_new.to_der().unwrap(), + snapshot: vec![], + targets: vec![], + target_files: Default::default(), + }; + let mut storage = MockStorage { + root: signed_root.to_der().unwrap(), + uncommitted_root: None, + timestamp: Some(signed_timestamp_old.to_der().unwrap()), + snapshot: Some(signed_snapshot.to_der().unwrap()), + targets: Some(signed_targets.to_der().unwrap()), + }; + update_timestamp( + &mut transport, + &mut storage, + &UtcTime::from_date_time(DateTime::new(2030, 1, 1, 0, 0, 0).unwrap()).unwrap(), + ) + .expect_err("this should fail"); + } +} diff --git a/tuf-no-std/tuf-no-std/src/signature.rs b/tuf-no-std/tuf-no-std/src/signature.rs new file mode 100644 index 0000000..fe764f6 --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/signature.rs @@ -0,0 +1,18 @@ +use crate::role::TufSignature; +#[cfg(feature = "der")] +use tuf_no_std_der::SignatureRef; + +#[cfg(feature = "der")] +impl<'a> TufSignature for SignatureRef<'a> { + fn raw_sig(&self) -> &[u8] { + self.sig.as_bytes().unwrap() + } + + fn keyid(&self) -> [u8; 32] { + self.keyid + .as_bytes() + .ok_or(()) + .and_then(|keyid| TryFrom::try_from(keyid).map_err(|_| ())) + .unwrap() + } +} diff --git a/tuf-no-std/tuf-no-std/src/utils.rs b/tuf-no-std/tuf-no-std/src/utils.rs new file mode 100644 index 0000000..5f49b2e --- /dev/null +++ b/tuf-no-std/tuf-no-std/src/utils.rs @@ -0,0 +1,382 @@ +#[cfg(any(test, feature = "memory-transport"))] +use der::asn1::BitString; +#[cfg(any(test, feature = "memory-transport"))] +use spki::SubjectPublicKeyInfoOwned; +#[cfg(any(test, feature = "memory-transport"))] +use tuf_no_std_common::common::Threshold; +#[cfg(any(test, feature = "memory-transport"))] +use tuf_no_std_der::{KeyId, Role}; + +#[cfg(any(test, feature = "memory-transport"))] +use alloc::vec::Vec; +#[cfg(any(test, feature = "memory-transport"))] +use core::num::NonZeroU64; + +#[cfg(any(test, feature = "memory-transport"))] +use tuf_no_std_common::remote::TufTransport; + +use tuf_no_std_common::storage::TufStorage; +use tuf_no_std_common::TufError; + +#[cfg(test)] +pub(crate) fn spki_from_signing_key( + key: &tuf_no_std_common::crypto::sign::SigningKey, +) -> SubjectPublicKeyInfoOwned { + key.as_spki().unwrap() +} + +#[cfg(test)] +pub(crate) fn build_role( + name: &str, + spkis: &[SubjectPublicKeyInfoOwned], + threshold: Threshold, +) -> Role { + Role { + keyids: spkis + .iter() + .map(|spki| KeyId::from_bytes(spki.fingerprint_bytes().unwrap().as_slice()).unwrap()) + .collect(), + threshold, + name: BitString::from_bytes(name.as_bytes()).unwrap(), + } +} + +#[cfg(test)] +#[derive(Default, Clone)] +pub(crate) struct MockStorage { + pub root: Vec, + pub uncommitted_root: Option>, + pub timestamp: Option>, + pub snapshot: Option>, + pub targets: Option>, +} + +#[cfg(test)] +impl TufStorage for MockStorage { + fn delete_timestamp_metadata(&mut self) { + self.timestamp = None + } + + fn delete_snapshot_metadata(&mut self) { + self.snapshot = None + } + + fn persist_root(&mut self, data: &[u8]) -> Result<(), TufError> { + self.uncommitted_root = Some(data.to_vec()); + Ok(()) + } + + fn persist_timestamp(&mut self, data: &[u8]) -> Result<(), TufError> { + self.timestamp = Some(data.to_vec()); + Ok(()) + } + + fn persist_snapshot(&mut self, data: &[u8]) -> Result<(), TufError> { + self.snapshot = Some(data.to_vec()); + Ok(()) + } + + fn persist_targets(&mut self, data: &[u8]) -> Result<(), TufError> { + self.targets = Some(data.to_vec()); + Ok(()) + } + + fn commit_root(&mut self) -> Result<(), TufError> { + //self.root = self.uncommitted_root.as_ref().expect("missing new root").clone(); + //self.uncommitted_root = None; + Ok(()) + } + + fn current_root(&self) -> &[u8] { + &self.root + } + + fn current_root_copy<'o>(&self, out: &'o mut [u8]) -> &'o [u8] { + out[..self.root.len()].copy_from_slice(self.root.as_slice()); + &out[..self.root.len()] + } + + fn current_uncommitted_root_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + match self.uncommitted_root.as_ref() { + None => None, + Some(root) => { + out[..root.len()].copy_from_slice(root.as_slice()); + Some(&out[..root.len()]) + } + } + } + + fn current_uncommitted_root(&self) -> Option<&[u8]> { + self.uncommitted_root.as_ref().map(AsRef::as_ref) + } + + fn current_timestamp(&self) -> Option<&[u8]> { + self.timestamp.as_ref().map(AsRef::as_ref) + } + + fn current_timestamp_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + let data = self.timestamp.as_deref()?; + let out = &mut out[..data.len()]; + out.copy_from_slice(data); + Some(out) + } + + fn current_snapshot(&self) -> Option<&[u8]> { + self.snapshot.as_ref().map(AsRef::as_ref) + } + + fn current_snapshot_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + let data = self.snapshot.as_deref()?; + let out = &mut out[..data.len()]; + out.copy_from_slice(data); + Some(out) + } + + fn current_targets(&self) -> Option<&[u8]> { + self.targets.as_ref().map(AsRef::as_ref) + } +} + +#[cfg(any(test, feature = "memory-transport"))] +#[derive(Default, Clone)] +pub struct MockTransport { + pub roots: heapless::FnvIndexMap, + pub timestamp: V, + pub snapshot: V, + pub targets: V, + pub target_files: heapless::FnvIndexMap, +} + +#[cfg(any(test, feature = "memory-transport"))] +fn check_size_and_copy<'o>( + src: &[u8], + out: &'o mut [u8], +) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + if out.len() < src.len() { + return Err(tuf_no_std_common::remote::TransportError::FetchError); + } + let (out_buf, _) = out.split_at_mut(src.len()); + out_buf.copy_from_slice(src); + Ok(out_buf) +} + +#[cfg(any(test, feature = "memory-transport"))] +impl, K: AsRef<[u8]>> TufTransport for MockTransport { + fn fetch_root<'o>( + &self, + version: NonZeroU64, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + let root_file = self + .roots + .get(&version) + .ok_or(tuf_no_std_common::remote::TransportError::FetchError)?; + check_size_and_copy(root_file.as_ref(), out) + } + + fn fetch_timestamp<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + check_size_and_copy(self.timestamp.as_ref(), out) + } + + fn fetch_snapshot<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + check_size_and_copy(self.snapshot.as_ref(), out) + } + + fn fetch_targets<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + check_size_and_copy(self.targets.as_ref(), out) + } + + fn fetch_target_file<'o>( + &self, + metapath: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + self.target_files + .iter() + .find(|(p, _)| p.as_ref() == metapath) + .ok_or(tuf_no_std_common::remote::TransportError::FetchError) + .and_then(|(_, f)| check_size_and_copy(f.as_ref(), out)) + } +} + +#[cfg(all(feature = "async", any(test, feature = "memory-transport")))] +impl, K: AsRef<[u8]>> tuf_no_std_common::remote::TufTransportAsync + for MockTransport +{ + async fn fetch_root<'o>( + &self, + version: NonZeroU64, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + ::fetch_root(self, version, out) + } + + async fn fetch_timestamp<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + ::fetch_timestamp(self, out) + } + + async fn fetch_snapshot<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + ::fetch_snapshot(self, out) + } + + async fn fetch_targets<'o>( + &self, + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + ::fetch_targets(self, out) + } + + async fn fetch_target_file<'o>( + &self, + metapath: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], tuf_no_std_common::remote::TransportError> { + ::fetch_target_file(self, metapath, out) + } +} + +/// In-memory implementation for TUF storage. Stores the files for the root, timestamp, snapshot and targets roles. +pub struct MemoryStorage { + pub root: heapless::Vec, + pub uncommitted_root: Option>, + pub timestamp: Option>, + pub snapshot: Option>, + pub targets: Option>, +} + +fn try_copy_from_slice(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], ()> { + let src = src.as_ref(); + if dst.len() < src.len() { + return Err(()); + } + let dst = &mut dst[..src.len()]; + dst.copy_from_slice(src); + Ok(dst) +} + +impl TufStorage for MemoryStorage { + fn delete_timestamp_metadata(&mut self) { + self.timestamp = None; + } + + fn delete_snapshot_metadata(&mut self) { + self.snapshot = None; + } + + fn persist_root(&mut self, data: &[u8]) -> Result<(), crate::common::TufError> { + self.uncommitted_root = Some( + heapless::Vec::<_, 2048>::from_slice(data) + .map_err(|_| TufError::CouldNotPersistRootMetadata)?, + ); + Ok(()) + } + + fn persist_timestamp(&mut self, data: &[u8]) -> Result<(), crate::common::TufError> { + self.timestamp = Some( + heapless::Vec::<_, 512>::from_slice(data) + .map_err(|_| TufError::CouldNotPersistRootMetadata)?, + ); + Ok(()) + } + + fn persist_snapshot(&mut self, data: &[u8]) -> Result<(), crate::common::TufError> { + self.snapshot = Some( + heapless::Vec::<_, 512>::from_slice(data) + .map_err(|_| TufError::CouldNotPersistRootMetadata)?, + ); + Ok(()) + } + + fn persist_targets(&mut self, data: &[u8]) -> Result<(), crate::common::TufError> { + self.targets = Some( + heapless::Vec::<_, 512>::from_slice(data) + .map_err(|_| TufError::CouldNotPersistRootMetadata)?, + ); + Ok(()) + } + + fn commit_root(&mut self) -> Result<(), crate::common::TufError> { + self.root = self + .uncommitted_root + .take() + .ok_or(TufError::InternalError)?; + Ok(()) + } + + fn current_root(&self) -> &[u8] { + self.root.as_ref() + } + + fn current_root_copy<'o>(&self, out: &'o mut [u8]) -> &'o [u8] { + try_copy_from_slice(&self.root, out).expect("this should be replaced with a Result") + } + + fn current_uncommitted_root_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + self.uncommitted_root + .as_ref() + .and_then(|data| try_copy_from_slice(data, out).ok()) + } + + fn current_uncommitted_root(&self) -> Option<&[u8]> { + self.uncommitted_root.as_ref().map(AsRef::as_ref) + } + + fn current_timestamp(&self) -> Option<&[u8]> { + self.timestamp.as_ref().map(AsRef::as_ref) + } + + fn current_timestamp_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + self.timestamp + .as_ref() + .and_then(|data| try_copy_from_slice(data, out).ok()) + } + + fn current_snapshot(&self) -> Option<&[u8]> { + self.snapshot.as_ref().map(AsRef::as_ref) + } + + fn current_snapshot_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + self.snapshot + .as_ref() + .and_then(|data| try_copy_from_slice(data, out).ok()) + } + + fn current_targets(&self) -> Option<&[u8]> { + self.targets.as_ref().map(AsRef::as_ref) + } +} + +#[cfg(test)] +mod test { + use super::try_copy_from_slice; + + #[test] + fn test_try_copy_from_slice() { + let src = [1, 2]; + let mut dst_0 = [0u8; 0]; + let mut dst_1 = [0u8; 1]; + let mut dst_2 = [0u8; 2]; + let mut dst_3 = [0u8; 3]; + let mut dst_4 = [0u8; 4]; + assert_eq!(try_copy_from_slice(src, &mut dst_0), Err(())); + assert_eq!(try_copy_from_slice(src, &mut dst_1), Err(())); + assert_eq!(try_copy_from_slice(src, &mut dst_2), Ok([1, 2].as_slice())); + assert_eq!(try_copy_from_slice(src, &mut dst_3), Ok([1, 2].as_slice())); + assert_eq!(try_copy_from_slice(src, &mut dst_4), Ok([1, 2].as_slice())); + } +} diff --git a/tuf-no-std/tuf-no-std/tests/integration_tests.rs b/tuf-no-std/tuf-no-std/tests/integration_tests.rs new file mode 100644 index 0000000..d6b202f --- /dev/null +++ b/tuf-no-std/tuf-no-std/tests/integration_tests.rs @@ -0,0 +1,282 @@ +use der::asn1::UtcTime; +use der::{DateTime, Decode, Encode}; +use rand_core::OsRng; +use std::collections::BTreeMap; +use std::num::NonZeroU64; +use tuf_no_std::builder::{RootBuilder, SnapshotBuilder, TargetsBuilder, TimestampBuilder}; +use tuf_no_std_common::crypto::sign::SigningKey; +use tuf_no_std_common::remote::{TransportError, TufTransport}; +use tuf_no_std_common::storage::TufStorage; +use tuf_no_std_common::TufError; +use tuf_no_std_der::root::Root; +use tuf_no_std_der::snapshot::Snapshot; +use tuf_no_std_der::targets::Targets; +use tuf_no_std_der::timestamp::Timestamp; +use tuf_no_std_der::Signed; + +fn gen_key() -> SigningKey { + let mut csprng = OsRng; + let key = ed25519_dalek::SigningKey::generate(&mut csprng); + + SigningKey::Ed25519Dalek(key) +} + +#[test] +fn test_out_timestamp() { + Signed::::from_der(include_bytes!("../../out/1.timestamp.der")) + .expect("failed to parse timestamp"); +} + +#[test] +fn test_out_root() { + Signed::::from_der(include_bytes!("../../out/1.root.der")).expect("failed to parse root"); +} + +#[test] +fn test_out_snapshot() { + Signed::::from_der(include_bytes!("../../out/1.snapshot.der")) + .expect("failed to parse snapshot"); +} + +#[test] +fn test_out_targets() { + Signed::::from_der(include_bytes!("../../out/1.targets.der")) + .expect("failed to parse targets"); +} + +#[test] +fn test() { + let target_file = b"Hello World!"; + + let root_key = gen_key(); + let timestamp_key = gen_key(); + let snapshot_key = gen_key(); + let targets_key = gen_key(); + + let root = RootBuilder::default() + .with_role_and_key("root", &[root_key.as_spki().unwrap()], 1) + .with_role_and_key("timestamp", &[timestamp_key.as_spki().unwrap()], 1) + .with_role_and_key("targets", &[targets_key.as_spki().unwrap()], 1) + .with_role_and_key("snapshot", &[snapshot_key.as_spki().unwrap()], 1) + .with_version(1) + .with_expiration_utc(2030, 1, 1, 1, 1, 1) + .build(); + + let targets = TargetsBuilder::default() + .with_expiration_utc(2030, 1, 1, 1, 1, 1) + .with_version(1) + .with_target(b"hello.txt", target_file) + .build(); + + let signed_root = Signed::from_signed(root, &[root_key]).unwrap(); + let targets_signed = Signed::from_signed(targets.clone(), &[targets_key]).unwrap(); + + let mut buf = [0u8; 4096]; + let targets_signed_encoded = targets_signed.encode_as_file(&mut buf).unwrap(); + let snapshot = SnapshotBuilder::default() + .with_expiration_utc(2030, 1, 1, 1, 1, 1) + .with_version(1) + .with_meta(b"targets.der", targets_signed_encoded, targets.version) + .build(); + + let signed_snapshot = Signed::from_signed(snapshot, &[snapshot_key]).unwrap(); + let timestamp = TimestampBuilder::default() + .with_expiration_utc(2030, 1, 1, 1, 1, 1) + .with_snapshot("snapshot.der", &signed_snapshot) + .with_version(1) + .build(); + + let signed_timestamp = Signed::from_signed(timestamp, &[timestamp_key]).unwrap(); + + let signed_root_encoded = signed_root.to_der().unwrap(); + let signed_timestamp_encoded = signed_timestamp.to_der().unwrap(); + let signed_snapshot_encoded = signed_snapshot.to_der().unwrap(); + let signed_targets_encoded = targets_signed.to_der().unwrap(); + + let mut storage = MockStorage { + root: signed_root_encoded.to_vec(), + uncommitted_root: None, + timestamp: None, + snapshot: None, + targets: None, + }; + let mut transport = MockTransport { + roots: BTreeMap::from([(NonZeroU64::new(1).unwrap(), signed_root_encoded.to_vec())]), + timestamp: signed_timestamp_encoded.to_vec(), + snapshot: signed_snapshot_encoded.to_vec(), + targets: signed_targets_encoded.to_vec(), + target_files: BTreeMap::from([("hello.txt".to_string(), target_file.to_vec())]), + }; + tuf_no_std::update_repo( + &mut storage, + &mut transport, + 10, + &UtcTime::from_date_time(DateTime::new(2023, 1, 1, 0, 0, 0).unwrap()).unwrap(), + ) + .expect("failed to verify valid repo"); + + let mut buf = [0u8; 2048]; + let output = tuf_no_std::fetch_and_verify_target_file( + &mut storage, + &mut transport, + b"hello.txt", + &mut buf, + ) + .expect("failed to fetch and verify file"); + assert_eq!(output, target_file); +} + +// impl TufRepo for Repo {} + +#[derive(Default, Clone)] +struct MockStorage { + pub root: Vec, + pub uncommitted_root: Option>, + pub timestamp: Option>, + pub snapshot: Option>, + pub targets: Option>, +} + +impl TufStorage for MockStorage { + fn delete_timestamp_metadata(&mut self) { + self.timestamp = None + } + + fn delete_snapshot_metadata(&mut self) { + self.snapshot = None + } + + fn persist_root(&mut self, data: &[u8]) -> Result<(), TufError> { + self.uncommitted_root = Some(data.to_vec()); + Ok(()) + } + + fn persist_timestamp(&mut self, data: &[u8]) -> Result<(), TufError> { + self.timestamp = Some(data.to_vec()); + Ok(()) + } + + fn persist_snapshot(&mut self, data: &[u8]) -> Result<(), TufError> { + self.snapshot = Some(data.to_vec()); + Ok(()) + } + + fn persist_targets(&mut self, data: &[u8]) -> Result<(), TufError> { + self.targets = Some(data.to_vec()); + Ok(()) + } + + fn commit_root(&mut self) -> Result<(), TufError> { + //self.root = self.uncommitted_root.as_ref().expect("missing new root").clone(); + //self.uncommitted_root = None; + Ok(()) + } + + fn current_root(&self) -> &[u8] { + &self.root + } + + fn current_root_copy<'o>(&self, out: &'o mut [u8]) -> &'o [u8] { + out[..self.root.len()].copy_from_slice(self.root.as_slice()); + &out[..self.root.len()] + } + + fn current_uncommitted_root_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + match self.uncommitted_root.as_ref() { + None => None, + Some(root) => { + out[..root.len()].copy_from_slice(root.as_slice()); + Some(&out[..root.len()]) + } + } + } + + fn current_uncommitted_root(&self) -> Option<&[u8]> { + self.uncommitted_root.as_ref().map(AsRef::as_ref) + } + + fn current_timestamp(&self) -> Option<&[u8]> { + self.timestamp.as_ref().map(AsRef::as_ref) + } + + fn current_timestamp_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + match self.timestamp.as_ref() { + None => None, + Some(root) => { + out[..root.len()].copy_from_slice(root.as_slice()); + Some(&out[..root.len()]) + } + } + } + + fn current_snapshot(&self) -> Option<&[u8]> { + self.snapshot.as_ref().map(AsRef::as_ref) + } + + fn current_snapshot_copy<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> { + match self.snapshot.as_ref() { + None => None, + Some(root) => { + out[..root.len()].copy_from_slice(root.as_slice()); + Some(&out[..root.len()]) + } + } + } + + fn current_targets(&self) -> Option<&[u8]> { + self.targets.as_ref().map(AsRef::as_ref) + } +} + +#[derive(Default, Clone)] +pub struct MockTransport { + pub roots: BTreeMap>, + pub timestamp: Vec, + pub snapshot: Vec, + pub targets: Vec, + pub target_files: BTreeMap>, +} + +fn check_size_and_copy<'o>(src: &[u8], out: &'o mut [u8]) -> Result<&'o [u8], TransportError> { + if out.len() < src.len() { + return Err(TransportError::FetchError); + } + let (out_buf, _) = out.split_at_mut(src.len()); + out_buf.copy_from_slice(src); + Ok(out_buf) +} + +impl TufTransport for MockTransport { + fn fetch_root<'o>( + &self, + version: NonZeroU64, + out: &'o mut [u8], + ) -> Result<&'o [u8], TransportError> { + let root_file = self.roots.get(&version).ok_or(TransportError::FetchError)?; + check_size_and_copy(root_file.as_slice(), out) + } + + fn fetch_timestamp<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError> { + check_size_and_copy(self.timestamp.as_slice(), out) + } + + fn fetch_snapshot<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError> { + check_size_and_copy(self.snapshot.as_slice(), out) + } + + fn fetch_targets<'o>(&self, out: &'o mut [u8]) -> Result<&'o [u8], TransportError> { + check_size_and_copy(self.targets.as_slice(), out) + } + + fn fetch_target_file<'o>( + &self, + metapath: &[u8], + out: &'o mut [u8], + ) -> Result<&'o [u8], TransportError> { + self.target_files + .iter() + .find(|(p, _)| p.as_bytes() == metapath) + .ok_or(TransportError::FetchError) + .and_then(|(_, f)| check_size_and_copy(f.as_slice(), out)) + } +}