From 11d42671783c00fe908b9aae06e629352ffb4082 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Fri, 5 Sep 2025 09:29:21 -0600 Subject: [PATCH 1/2] Simulate tech port unlock Depends on management-gateway-service commit c80fb4f23108691dea43239b55a14f5e47f161b7. --- Cargo.lock | 109 +++++++++-------- Cargo.toml | 10 +- sp-sim/Cargo.toml | 4 + sp-sim/examples/config.toml | 1 + sp-sim/src/config.rs | 1 + sp-sim/src/gimlet.rs | 13 ++ sp-sim/src/sidecar.rs | 232 ++++++++++++++++++++++++++++++++++-- workspace-hack/Cargo.toml | 48 ++++---- 8 files changed, 335 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af39e0787f9..59c77e812f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -714,7 +714,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cexpr", "clang-sys", "itertools 0.12.1", @@ -737,7 +737,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cexpr", "clang-sys", "itertools 0.13.0", @@ -806,9 +806,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] @@ -2020,7 +2020,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "crossterm_winapi", "futures-core", "mio", @@ -2038,7 +2038,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "crossterm_winapi", "derive_more 2.0.1", "document-features", @@ -2643,7 +2643,7 @@ version = "2.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229850a212cd9b84d4f0290ad9d294afc0ae70fccaa8949dbe8b43ffafa1e20c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "byteorder", "chrono", "diesel_derives", @@ -3728,7 +3728,7 @@ dependencies = [ [[package]] name = "gateway-ereport-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=77e316c812aa057b9714d0d99c4a7bdd36d45be2#77e316c812aa057b9714d0d99c4a7bdd36d45be2" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=c80fb4f23108691dea43239b55a14f5e47f161b7#c80fb4f23108691dea43239b55a14f5e47f161b7" dependencies = [ "serde", "zerocopy 0.8.26", @@ -3737,9 +3737,9 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=77e316c812aa057b9714d0d99c4a7bdd36d45be2#77e316c812aa057b9714d0d99c4a7bdd36d45be2" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=c80fb4f23108691dea43239b55a14f5e47f161b7#c80fb4f23108691dea43239b55a14f5e47f161b7" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "hubpack", "serde", "serde-big-array", @@ -3747,7 +3747,7 @@ dependencies = [ "smoltcp 0.9.1", "static_assertions", "strum 0.27.2", - "strum_macros 0.27.1", + "strum_macros 0.27.2", "uuid", "zerocopy 0.8.26", ] @@ -3755,7 +3755,7 @@ dependencies = [ [[package]] name = "gateway-sp-comms" version = "0.1.1" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=77e316c812aa057b9714d0d99c4a7bdd36d45be2#77e316c812aa057b9714d0d99c4a7bdd36d45be2" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=c80fb4f23108691dea43239b55a14f5e47f161b7#c80fb4f23108691dea43239b55a14f5e47f161b7" dependencies = [ "async-trait", "backoff", @@ -3779,9 +3779,9 @@ dependencies = [ "serde_json", "slog", "slog-error-chain", - "socket2 0.5.10", + "socket2 0.6.0", "string_cache", - "thiserror 1.0.69", + "thiserror 2.0.16", "tlvc 0.3.1 (git+https://github.com/oxidecomputer/tlvc.git?branch=main)", "tokio", "usdt", @@ -3925,7 +3925,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", "libgit2-sys", "log", @@ -3957,7 +3957,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "ignore", "walkdir", ] @@ -4896,7 +4896,7 @@ name = "illumos-sys-hdrs" version = "0.1.0" source = "git+https://github.com/oxidecomputer/opte?rev=6a5d3f336685a2aeb291962ae7f583b9355f3022#6a5d3f336685a2aeb291962ae7f583b9355f3022" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -5025,7 +5025,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a17a93829808685f3b6882763901d7489efc1155ad4ae568499d1b303067ca6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "ingot-macros", "ingot-types", "macaddr", @@ -5274,7 +5274,7 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "libc", ] @@ -5713,7 +5713,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", "redox_syscall 0.5.7", ] @@ -7176,7 +7176,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "libc", "memoffset", @@ -7188,7 +7188,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "cfg_aliases 0.1.1", "libc", @@ -7200,7 +7200,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -7213,7 +7213,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -8499,20 +8499,23 @@ dependencies = [ name = "omicron-workspace-hack" version = "0.1.0" dependencies = [ + "aead", + "aes-gcm", "ahash", "aho-corasick", "anyhow", "aws-lc-rs", "base16ct", "base64 0.22.1", - "base64ct", + "bcrypt-pbkdf", "bitflags 1.3.2", - "bitflags 2.9.1", + "bitflags 2.9.4", "bstr", "buf-list", "byteorder", "bytes", "cc", + "chacha20", "chrono", "cipher", "clang-sys", @@ -8581,8 +8584,8 @@ dependencies = [ "num-traits", "once_cell", "openapiv3", + "p256", "peg-runtime", - "pem-rfc7468", "percent-encoding", "petgraph 0.6.5", "phf_shared 0.11.2", @@ -8616,8 +8619,9 @@ dependencies = [ "similar", "slog", "smallvec 1.15.1", - "socket2 0.5.10", "spin", + "ssh-cipher", + "ssh-key", "string_cache", "strum 0.26.3", "strum 0.27.2", @@ -8790,7 +8794,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "foreign-types 0.3.2", "libc", @@ -8833,7 +8837,7 @@ name = "opte" version = "0.1.0" source = "git+https://github.com/oxidecomputer/opte?rev=6a5d3f336685a2aeb291962ae7f583b9355f3022#6a5d3f336685a2aeb291962ae7f583b9355f3022" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "dyn-clone", "illumos-sys-hdrs", "ingot", @@ -10564,7 +10568,7 @@ checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set 0.8.0", "bit-vec 0.8.0", - "bitflags 2.9.1", + "bitflags 2.9.4", "lazy_static", "num-traits", "rand 0.9.2", @@ -10836,7 +10840,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cassowary", "compact_str", "crossterm 0.28.1", @@ -10979,7 +10983,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -11218,7 +11222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.9.1", + "bitflags 2.9.4", "serde", "serde_derive", ] @@ -11305,7 +11309,7 @@ dependencies = [ "aes", "aes-gcm", "async-trait", - "bitflags 2.9.1", + "bitflags 2.9.4", "byteorder", "cbc", "chacha20", @@ -11453,7 +11457,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.4.14", @@ -11466,7 +11470,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.9.4", @@ -11615,7 +11619,7 @@ version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "clipboard-win", "fd-lock", @@ -11830,7 +11834,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -11843,7 +11847,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -12816,6 +12820,7 @@ dependencies = [ "async-trait", "clap", "dropshot", + "ecdsa", "futures", "gateway-ereport-messages", "gateway-messages", @@ -12827,12 +12832,15 @@ dependencies = [ "omicron-common", "omicron-workspace-hack", "oxide-tokio-rt", + "p256", + "rand 0.9.2", "serde", "serde_cbor", "sha2", "sha3", "slog", "slog-dtrace", + "ssh-key", "thiserror 2.0.16", "tokio", "toml 0.8.23", @@ -12950,9 +12958,9 @@ dependencies = [ [[package]] name = "ssh-key" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" dependencies = [ "bcrypt-pbkdf", "ed25519-dalek", @@ -13088,7 +13096,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.27.1", + "strum_macros 0.27.2", ] [[package]] @@ -13119,14 +13127,13 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "rustversion", "syn 2.0.106", ] @@ -13231,7 +13238,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -13994,7 +14001,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "bytes", "futures-util", "http", @@ -14178,7 +14185,7 @@ name = "transceiver-messages" version = "0.1.1" source = "git+https://github.com/oxidecomputer/transceiver-control?branch=main#f3cb309c2bd2c03423467fd93992e9033ae3133c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "clap", "hubpack", "schemars", @@ -14191,7 +14198,7 @@ name = "transceiver-messages" version = "0.1.1" source = "git+https://github.com/oxidecomputer/transceiver-control#4aac6125a8e6cefbb71d9f8a3d1fe6704207d476" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "clap", "hubpack", "schemars", @@ -15852,7 +15859,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2ae983777bc..c0b34d0b205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -435,6 +435,7 @@ dns-service-client = { path = "clients/dns-service-client" } dpd-client = { git = "https://github.com/oxidecomputer/dendrite", rev = "8314881e372d7bbb4a4ee2da051ecdc34f66c534" } dropshot = { version = "0.16.4", features = [ "usdt-probes" ] } dyn-clone = "1.0.20" +ecdsa = { version = "0.16.9", features = ["pem", "signing", "std", "verifying"] } either = "1.15.0" ereport-types = { path = "ereport/types" } expectorate = "1.2.0" @@ -457,9 +458,9 @@ gateway-client = { path = "clients/gateway-client" } # compatibility, but will mean that faux-mgs might be missing new # functionality.) # -gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "77e316c812aa057b9714d0d99c4a7bdd36d45be2", default-features = false, features = ["debug-impls"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "77e316c812aa057b9714d0d99c4a7bdd36d45be2", default-features = false, features = ["std"] } -gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "77e316c812aa057b9714d0d99c4a7bdd36d45be2" } +gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c80fb4f23108691dea43239b55a14f5e47f161b7", default-features = false, features = ["debug-impls"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c80fb4f23108691dea43239b55a14f5e47f161b7", default-features = false, features = ["std"] } +gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c80fb4f23108691dea43239b55a14f5e47f161b7" } gateway-test-utils = { path = "gateway-test-utils" } gateway-types = { path = "gateway-types" } gethostname = "0.5.0" @@ -678,7 +679,7 @@ semver = { version = "1.0.26", features = ["std", "serde"] } serde = { version = "1.0", default-features = false, features = [ "derive", "rc" ] } serde_cbor = "0.11.2" serde_human_bytes = { git = "https://github.com/oxidecomputer/serde_human_bytes", branch = "main" } -serde_json = "1.0.142" +serde_json = "1.0.143" serde_tokenstream = "0.2" serde_urlencoded = "0.7.1" serde_with = { version = "3.14.0", default-features = false, features = ["alloc", "macros"] } @@ -719,6 +720,7 @@ sp-sim = { path = "sp-sim" } sprockets-tls = { git = "https://github.com/oxidecomputer/sprockets.git", rev = "6d31fa63217c6a51061dc4afa1ebe175a0021981" } sqlformat = "0.3.5" sqlparser = { version = "0.45.0", features = [ "visitor" ] } +ssh-key = "0.6.7" static_assertions = "1.1.0" # Please do not change the Steno version to a Git dependency. It makes it # harder than expected to make breaking changes (even if you specify a specific diff --git a/sp-sim/Cargo.toml b/sp-sim/Cargo.toml index 0f453d416c8..53ccd400f04 100644 --- a/sp-sim/Cargo.toml +++ b/sp-sim/Cargo.toml @@ -12,6 +12,7 @@ anyhow.workspace = true async-trait.workspace = true clap.workspace = true dropshot.workspace = true +ecdsa.workspace = true futures.workspace = true gateway-ereport-messages.workspace = true gateway-messages.workspace = true @@ -21,6 +22,9 @@ hubtools.workspace = true nexus-types.workspace = true omicron-common.workspace = true oxide-tokio-rt.workspace = true +p256.workspace = true +rand.workspace = true +ssh-key.workspace = true serde.workspace = true serde_cbor.workspace = true sha2.workspace = true diff --git a/sp-sim/examples/config.toml b/sp-sim/examples/config.toml index 35f6ad46529..e4077b2f01f 100644 --- a/sp-sim/examples/config.toml +++ b/sp-sim/examples/config.toml @@ -6,6 +6,7 @@ serial_number = "SimSidecar0" manufacturing_root_cert_seed = "01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de" device_id_cert_seed = "01de000000000000000000000000000000000000000000000000000000000000" +authorized_keys = "../smf/switch_zone_setup/support_authorized_keys" [[simulated_sps.sidecar.network_config]] [simulated_sps.sidecar.network_config.simulated] diff --git a/sp-sim/src/config.rs b/sp-sim/src/config.rs index 418bc5125cd..4b51f0533da 100644 --- a/sp-sim/src/config.rs +++ b/sp-sim/src/config.rs @@ -143,6 +143,7 @@ pub struct SpComponentConfig { pub struct SidecarConfig { #[serde(flatten)] pub common: SpCommonConfig, + pub authorized_keys: Option, } /// Configuration of a simulated gimlet SP diff --git a/sp-sim/src/gimlet.rs b/sp-sim/src/gimlet.rs index f4ab4600dce..e2eee60ae67 100644 --- a/sp-sim/src/gimlet.rs +++ b/sp-sim/src/gimlet.rs @@ -31,6 +31,7 @@ use gateway_messages::DumpTask; use gateway_messages::Header; use gateway_messages::MgsRequest; use gateway_messages::MgsResponse; +use gateway_messages::MonorailError; use gateway_messages::PowerStateTransition; use gateway_messages::RotBootInfo; use gateway_messages::RotRequest; @@ -40,6 +41,8 @@ use gateway_messages::SpError; use gateway_messages::SpPort; use gateway_messages::SpRequest; use gateway_messages::SpStateV2; +use gateway_messages::UnlockChallenge; +use gateway_messages::UnlockResponse; use gateway_messages::ignition::{self, LinkEvents}; use gateway_messages::sp_impl::Sender; use gateway_messages::sp_impl::SpHandler; @@ -1722,6 +1725,16 @@ impl SpHandler for Handler { fn get_host_flash_hash(&mut self, slot: u16) -> Result<[u8; 32], SpError> { self.update_state.get_host_flash_hash(slot) } + + fn unlock( + &mut self, + _vid: Self::VLanId, + _challenge: UnlockChallenge, + _response: UnlockResponse, + _time_sec: u32, + ) -> Result<(), MonorailError> { + Err(MonorailError::UnlockFailed) + } } impl SimSpHandler for Handler { diff --git a/sp-sim/src/sidecar.rs b/sp-sim/src/sidecar.rs index 828049b8807..012be15dca9 100644 --- a/sp-sim/src/sidecar.rs +++ b/sp-sim/src/sidecar.rs @@ -19,7 +19,7 @@ use crate::server::SimSpHandler; use crate::server::UdpServer; use crate::update::BaseboardKind; use crate::update::SimSpUpdate; -use anyhow::Result; +use anyhow::{Context, Result}; use async_trait::async_trait; use futures::Future; use futures::future; @@ -32,11 +32,15 @@ use gateway_messages::DumpCompression; use gateway_messages::DumpError; use gateway_messages::DumpSegment; use gateway_messages::DumpTask; +use gateway_messages::EcdsaSha2Nistp256Challenge; use gateway_messages::IgnitionCommand; use gateway_messages::IgnitionState; use gateway_messages::MgsError; use gateway_messages::MgsRequest; use gateway_messages::MgsResponse; +use gateway_messages::MonorailComponentAction; +use gateway_messages::MonorailComponentActionResponse; +use gateway_messages::MonorailError; use gateway_messages::PowerState; use gateway_messages::RotBootInfo; use gateway_messages::RotRequest; @@ -46,6 +50,8 @@ use gateway_messages::SpError; use gateway_messages::SpPort; use gateway_messages::SpStateV2; use gateway_messages::StartupOptions; +use gateway_messages::UnlockChallenge; +use gateway_messages::UnlockResponse; use gateway_messages::ignition; use gateway_messages::ignition::IgnitionError; use gateway_messages::ignition::LinkEvents; @@ -54,10 +60,14 @@ use gateway_messages::sp_impl::DeviceDescription; use gateway_messages::sp_impl::Sender; use gateway_messages::sp_impl::SpHandler; use gateway_types::component::SpState; +use rand::TryRngCore; +use rand::rngs::OsRng; use slog::Logger; use slog::debug; use slog::info; use slog::warn; +use ssh_key::AuthorizedKeys; +use ssh_key::PublicKey; use std::collections::HashMap; use std::iter; use std::net::SocketAddrV6; @@ -65,6 +75,8 @@ use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; use tokio::select; use tokio::sync::Mutex as TokioMutex; use tokio::sync::mpsc; @@ -72,7 +84,10 @@ use tokio::sync::oneshot; use tokio::sync::watch; use tokio::task; use tokio::task::JoinHandle; +use zerocopy::IntoBytes; +const CHALLENGE_EXPIRATION_TIME_SECS: u64 = 10; +const MAX_UNLOCK_TIME_SECS: u32 = 600; pub const SIM_SIDECAR_BOARD: &str = "SimSidecarSp"; pub struct Sidecar { @@ -206,6 +221,20 @@ impl Sidecar { let (commands, commands_rx) = mpsc::unbounded_channel(); + let trusted_keys = match &sidecar.authorized_keys { + None => Vec::new(), + Some(authorized_keys) => AuthorizedKeys::read_file(authorized_keys) + .with_context(|| { + format!( + "failed to read authorized keys from {}", + authorized_keys.display() + ) + })? + .into_iter() + .map(|entry| entry.public_key().clone()) + .collect(), + }; + if let Some(network_config) = &sidecar.common.network_config { // bind to our two local "KSZ" ports let servers = future::try_join( @@ -276,6 +305,7 @@ impl Sidecar { sidecar.common.old_rot_state, update_state, Arc::clone(&power_state_changes), + trusted_keys, ); let inner_task = task::spawn(async move { inner.run().await.unwrap() }); @@ -348,6 +378,7 @@ impl Inner { old_rot_state: bool, update_state: SimSpUpdate, power_state_changes: Arc, + trusted_keys: Vec, ) -> (Self, Arc>, watch::Receiver) { let [udp0, udp1] = servers; let handler = Arc::new(TokioMutex::new(Handler::new( @@ -358,6 +389,7 @@ impl Inner { old_rot_state, update_state, power_state_changes, + trusted_keys, ))); let responses_sent_count = watch::Sender::new(0); let responses_sent_count_rx = responses_sent_count.subscribe(); @@ -512,9 +544,15 @@ struct Handler { should_fail_to_respond_signal: Option>, old_rot_state: bool, sp_dumps: HashMap<[u8; 16], u32>, + + // To simulate tech port unlock, we must store the set of trusted keys and + // the latest challenge together with the time at which it was issued. + trusted_keys: Vec, + last_challenge: Option<(UnlockChallenge, u64)>, } impl Handler { + #[allow(clippy::too_many_arguments)] fn new( serial_number: String, components: Vec, @@ -523,6 +561,7 @@ impl Handler { old_rot_state: bool, update_state: SimSpUpdate, power_state_changes: Arc, + trusted_keys: Vec, ) -> Self { let mut leaked_component_device_strings = Vec::with_capacity(components.len()); @@ -555,6 +594,8 @@ impl Handler { should_fail_to_respond_signal: None, old_rot_state, sp_dumps, + trusted_keys, + last_challenge: None, } } @@ -1061,13 +1102,102 @@ impl SpHandler for Handler { component: SpComponent, action: ComponentAction, ) -> Result { - warn!( - &self.log, "asked to perform component action (not supported for sim components)"; - "sender" => ?sender, - "component" => ?component, - "action" => ?action, - ); - Err(SpError::RequestUnsupportedForComponent) + match action { + ComponentAction::Monorail( + MonorailComponentAction::RequestChallenge, + ) => { + // Emulate a `LifecycleState::Release` device and issue + // an ECDSA challenge. + let (challenge, time) = get_ecdsa_challenge()?; + let challenge = UnlockChallenge::EcdsaSha2Nistp256(challenge); + self.last_challenge = Some((challenge, time)); + Ok(ComponentActionResponse::Monorail( + MonorailComponentActionResponse::RequestChallenge( + challenge, + ), + )) + } + ComponentAction::Monorail(MonorailComponentAction::Unlock { + challenge, + response, + time_sec, + }) => self + .unlock(sender.vid, challenge, response, time_sec) + .map_err(SpError::Monorail) + .map(|()| ComponentActionResponse::Ack), + ComponentAction::Monorail(MonorailComponentAction::Lock) => { + Ok(ComponentActionResponse::Ack) + } + ComponentAction::Led(_) => { + warn!( + &self.log, "asked to perform LED component action (not supported for sim components)"; + "sender" => ?sender, + "component" => ?component, + "action" => ?action, + ); + Err(SpError::RequestUnsupportedForComponent) + } + } + } + + /// Pretends to unlock the tech port if the challenge and response are compatible + fn unlock( + &mut self, + _vid: Self::VLanId, + challenge: UnlockChallenge, + response: UnlockResponse, + time_sec: u32, + ) -> Result<(), MonorailError> { + if time_sec > MAX_UNLOCK_TIME_SECS { + warn!(&self.log, "unlock time too long"; "time_sec" => time_sec); + return Err(MonorailError::TimeIsTooLong); + } + + // Callers only get one attempt per challenge; if they fail to authorize + // the unlock, they have to ask for a new challenge. + let Some((last_challenge, challenge_time)) = self.last_challenge.take() + else { + warn!(&self.log, "no challenge for monorail unlock"); + return Err(MonorailError::UnlockAuthFailed); + }; + + if challenge != last_challenge { + warn!(&self.log, "wrong challenge for monorail unlock"); + return Err(MonorailError::UnlockAuthFailed); + } + + if now() >= challenge_time + CHALLENGE_EXPIRATION_TIME_SECS { + warn!(&self.log, "challenge expired"); + return Err(MonorailError::UnlockAuthFailed); + } + + // Check that the response is valid for our current challenge + // and trusted keys. + match (challenge, response) { + ( + UnlockChallenge::Trivial { timestamp: ts1 }, + UnlockResponse::Trivial { timestamp: ts2 }, + ) if ts1 == ts2 => Ok(()), + ( + UnlockChallenge::EcdsaSha2Nistp256(data), + UnlockResponse::EcdsaSha2Nistp256 { + key, + signer_nonce, + signature, + }, + ) => verify_signature( + &self.log, + &self.trusted_keys, + &data, + &key, + &signer_nonce, + &signature, + ), + _ => Err(MonorailError::UnlockAuthFailed), + }?; + debug!(&self.log, "unlock auth succeeded"); + + Ok(()) } fn get_startup_options(&mut self) -> Result { @@ -1454,3 +1584,89 @@ impl FakeIgnition { Ok(()) } } + +/// The current Unix time, i.e., seconds since the epoch. +fn now() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("should be able to get Unix time") + .as_secs() +} + +/// Generate a fresh ECDSA tech port unlock challenge. +fn get_ecdsa_challenge() -> Result<(EcdsaSha2Nistp256Challenge, u64), SpError> { + // Get a nonce from the OS RNG. + let mut nonce = [0u8; 32]; + OsRng.try_fill_bytes(&mut nonce).expect("OS out of entropy"); + + // Get the current time from the OS clock. + let now = now(); + + Ok(( + EcdsaSha2Nistp256Challenge { + hw_id: [0; _], // fake + sw_id: [0, 0, 0, 1], // placeholder + time: now.to_le_bytes(), + nonce, + }, + now, + )) +} + +fn verify_signature( + log: &Logger, + trusted_keys: &[PublicKey], + data: &EcdsaSha2Nistp256Challenge, + key: &[u8; 65], + signer_nonce: &[u8; 8], + signature: &[u8; 64], +) -> Result<(), MonorailError> { + if !trusted_keys.iter().any(|t| { + t.key_data().ecdsa().expect("must be ECDSA key").as_sec1_bytes() == key + }) { + warn!(log, "wrong key for tech port unlock"); + return Err(MonorailError::UnlockAuthFailed); + } + + let sig = p256::ecdsa::Signature::from_bytes(signature.as_slice().into()) + .map_err(|_| MonorailError::UnlockAuthFailed)?; + + let v = p256::ecdsa::VerifyingKey::from_encoded_point( + &p256::EncodedPoint::from_bytes(key) + .map_err(|_| MonorailError::UnlockAuthFailed)?, + ) + .map_err(|_| MonorailError::UnlockAuthFailed)?; + + // Build an SSH signature blob to be verified + // + // See https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig + // for the signature blob format + #[rustfmt::skip] + let mut buf = vec![ + // MAGIC_PREAMBLE + b'S', b'S', b'H', b'S', b'I', b'G', + + // namespace + 0, 0, 0, 15, // length + b'm', b'o', b'n', b'o', b'r', b'a', b'i', b'l', b'-', + b'u', b'n', b'l', b'o', b'c', b'k', + + // reserved + 0, 0, 0, 0, + + // hash type + 0, 0, 0, 6, // length + b's', b'h', b'a', b'2', b'5', b'6', + ]; + + let mut hasher = sha2::Sha256::new(); + use sha2::Digest; + hasher.update(data.as_bytes()); + hasher.update(signer_nonce); + let hash = hasher.finalize(); + buf.extend_from_slice(&(hash.len() as u32).to_be_bytes()); + buf.extend_from_slice(&hash); + + use p256::ecdsa::signature::Verifier; + v.verify(&buf, &sig).map_err(|_| MonorailError::UnlockAuthFailed) +} diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 1789edf8663..15e5e94d5cc 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -18,19 +18,22 @@ workspace = true ### BEGIN HAKARI SECTION [dependencies] +aead = { version = "0.5.2", default-features = false, features = ["alloc", "getrandom"] } +aes-gcm = { version = "0.10.3" } ahash = { version = "0.8.12" } aho-corasick = { version = "1.1.3" } anyhow = { version = "1.0.99", features = ["backtrace"] } aws-lc-rs = { version = "1.12.4", features = ["prebuilt-nasm"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } base64 = { version = "0.22.1" } -base64ct = { version = "1.6.0", default-features = false, features = ["std"] } +bcrypt-pbkdf = { version = "0.10.0" } bitflags-dff4ba8e3ae991db = { package = "bitflags", version = "1.3.2" } -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["serde"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["serde"] } bstr = { version = "1.10.0" } buf-list = { version = "1.0.3", default-features = false, features = ["tokio1"] } byteorder = { version = "1.5.0" } bytes = { version = "1.10.1", features = ["serde"] } +chacha20 = { version = "0.9.1", default-features = false, features = ["zeroize"] } chrono = { version = "0.4.41", features = ["serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } clap = { version = "4.5.41", features = ["cargo", "derive", "env", "wrap_help"] } @@ -59,8 +62,8 @@ futures-io = { version = "0.3.31" } futures-sink = { version = "0.3.31" } futures-task = { version = "0.3.31", default-features = false, features = ["std"] } futures-util = { version = "0.3.31", features = ["channel", "io", "sink"] } -gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "77e316c812aa057b9714d0d99c4a7bdd36d45be2", default-features = false, features = ["debug-impls", "serde"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "77e316c812aa057b9714d0d99c4a7bdd36d45be2", features = ["std"] } +gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c80fb4f23108691dea43239b55a14f5e47f161b7", default-features = false, features = ["debug-impls", "serde"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c80fb4f23108691dea43239b55a14f5e47f161b7", features = ["std"] } generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.15", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } @@ -89,8 +92,8 @@ num-iter = { version = "0.1.45", default-features = false, features = ["i128"] } num-traits = { version = "0.2.19", features = ["i128", "libm"] } once_cell = { version = "1.21.3", features = ["critical-section"] } openapiv3 = { version = "2.2.0", default-features = false, features = ["skip_serializing_defaults"] } +p256 = { version = "0.13.2", features = ["ecdh"] } peg-runtime = { version = "0.8.5", default-features = false, features = ["std"] } -pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] } percent-encoding = { version = "2.3.2" } petgraph = { version = "0.6.5", features = ["serde-1"] } phf_shared = { version = "0.11.2" } @@ -122,8 +125,9 @@ sha2 = { version = "0.10.9", features = ["oid"] } similar = { version = "2.7.0", features = ["bytes", "inline", "unicode"] } slog = { version = "2.7.0", features = ["dynamic-keys", "max_level_trace", "release_max_level_debug", "release_max_level_trace"] } smallvec = { version = "1.15.1", default-features = false, features = ["const_new"] } -socket2 = { version = "0.5.10", default-features = false, features = ["all"] } spin = { version = "0.9.8" } +ssh-cipher = { version = "0.2.0", default-features = false, features = ["aes-cbc", "aes-ctr", "aes-gcm", "chacha20poly1305"] } +ssh-key = { version = "0.6.7", features = ["ed25519", "encryption", "rsa"] } string_cache = { version = "0.8.9" } strum-2f80eeee3b1b6c7e = { package = "strum", version = "0.26.3", features = ["derive"] } strum-754bda37e0fb3874 = { package = "strum", version = "0.27.2", features = ["derive"] } @@ -154,20 +158,23 @@ zip-164d15cefe24d7eb = { package = "zip", version = "4.2.0", default-features = zip-3b31131e45eafb45 = { package = "zip", version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] } [build-dependencies] +aead = { version = "0.5.2", default-features = false, features = ["alloc", "getrandom"] } +aes-gcm = { version = "0.10.3" } ahash = { version = "0.8.12" } aho-corasick = { version = "1.1.3" } anyhow = { version = "1.0.99", features = ["backtrace"] } aws-lc-rs = { version = "1.12.4", features = ["prebuilt-nasm"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } base64 = { version = "0.22.1" } -base64ct = { version = "1.6.0", default-features = false, features = ["std"] } +bcrypt-pbkdf = { version = "0.10.0" } bitflags-dff4ba8e3ae991db = { package = "bitflags", version = "1.3.2" } -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["serde"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["serde"] } bstr = { version = "1.10.0" } buf-list = { version = "1.0.3", default-features = false, features = ["tokio1"] } byteorder = { version = "1.5.0" } bytes = { version = "1.10.1", features = ["serde"] } cc = { version = "1.2.30", default-features = false, features = ["parallel"] } +chacha20 = { version = "0.9.1", default-features = false, features = ["zeroize"] } chrono = { version = "0.4.41", features = ["serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } clap = { version = "4.5.41", features = ["cargo", "derive", "env", "wrap_help"] } @@ -196,8 +203,8 @@ futures-io = { version = "0.3.31" } futures-sink = { version = "0.3.31" } futures-task = { version = "0.3.31", default-features = false, features = ["std"] } futures-util = { version = "0.3.31", features = ["channel", "io", "sink"] } -gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "77e316c812aa057b9714d0d99c4a7bdd36d45be2", default-features = false, features = ["debug-impls", "serde"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "77e316c812aa057b9714d0d99c4a7bdd36d45be2", features = ["std"] } +gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c80fb4f23108691dea43239b55a14f5e47f161b7", default-features = false, features = ["debug-impls", "serde"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "c80fb4f23108691dea43239b55a14f5e47f161b7", features = ["std"] } generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.15", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } @@ -226,8 +233,8 @@ num-iter = { version = "0.1.45", default-features = false, features = ["i128"] } num-traits = { version = "0.2.19", features = ["i128", "libm"] } once_cell = { version = "1.21.3", features = ["critical-section"] } openapiv3 = { version = "2.2.0", default-features = false, features = ["skip_serializing_defaults"] } +p256 = { version = "0.13.2", features = ["ecdh"] } peg-runtime = { version = "0.8.5", default-features = false, features = ["std"] } -pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] } percent-encoding = { version = "2.3.2" } petgraph = { version = "0.6.5", features = ["serde-1"] } phf_shared = { version = "0.11.2" } @@ -259,8 +266,9 @@ sha2 = { version = "0.10.9", features = ["oid"] } similar = { version = "2.7.0", features = ["bytes", "inline", "unicode"] } slog = { version = "2.7.0", features = ["dynamic-keys", "max_level_trace", "release_max_level_debug", "release_max_level_trace"] } smallvec = { version = "1.15.1", default-features = false, features = ["const_new"] } -socket2 = { version = "0.5.10", default-features = false, features = ["all"] } spin = { version = "0.9.8" } +ssh-cipher = { version = "0.2.0", default-features = false, features = ["aes-cbc", "aes-ctr", "aes-gcm", "chacha20poly1305"] } +ssh-key = { version = "0.6.7", features = ["ed25519", "encryption", "rsa"] } string_cache = { version = "0.8.9" } strum-2f80eeee3b1b6c7e = { package = "strum", version = "0.26.3", features = ["derive"] } strum-754bda37e0fb3874 = { package = "strum", version = "0.27.2", features = ["derive"] } @@ -293,7 +301,7 @@ zip-164d15cefe24d7eb = { package = "zip", version = "4.2.0", default-features = zip-3b31131e45eafb45 = { package = "zip", version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] } [target.x86_64-unknown-linux-gnu.dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } dof = { version = "0.3.0", default-features = false, features = ["des"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.1", default-features = false, features = ["std"] } @@ -305,7 +313,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38.37", features = [ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1.0.7", features = ["fs", "stdio", "termios"] } [target.x86_64-unknown-linux-gnu.build-dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } dof = { version = "0.3.0", default-features = false, features = ["des"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.1", default-features = false, features = ["std"] } @@ -317,7 +325,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38.37", features = [ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1.0.7", features = ["fs", "stdio", "termios"] } [target.x86_64-apple-darwin.dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.1", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2", "ring", "webpki-tokio"] } @@ -327,7 +335,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38.37", features = [ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1.0.7", features = ["fs", "stdio", "termios"] } [target.x86_64-apple-darwin.build-dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.1", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2", "ring", "webpki-tokio"] } @@ -337,7 +345,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38.37", features = [ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1.0.7", features = ["fs", "stdio", "termios"] } [target.aarch64-apple-darwin.dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.1", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2", "ring", "webpki-tokio"] } @@ -347,7 +355,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38.37", features = [ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1.0.7", features = ["fs", "stdio", "termios"] } [target.aarch64-apple-darwin.build-dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.1", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2", "ring", "webpki-tokio"] } @@ -357,7 +365,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38.37", features = [ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1.0.7", features = ["fs", "stdio", "termios"] } [target.x86_64-unknown-illumos.dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } dof = { version = "0.3.0", default-features = false, features = ["des"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.1", default-features = false, features = ["std"] } @@ -372,7 +380,7 @@ toml_edit-cdcf2f9584511fe6 = { package = "toml_edit", version = "0.19.15", featu winnow-3b31131e45eafb45 = { package = "winnow", version = "0.6.26", features = ["simd"] } [target.x86_64-unknown-illumos.build-dependencies] -bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.1", default-features = false, features = ["std"] } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.9.4", default-features = false, features = ["std"] } clang-sys = { version = "1.8.1", default-features = false, features = ["clang_11_0", "runtime"] } cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } dof = { version = "0.3.0", default-features = false, features = ["des"] } From 14a5fa9c35559ec63525dca1e6d407c856e62270 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Tue, 30 Sep 2025 12:40:22 -0600 Subject: [PATCH 2/2] Move tech port unlock out of SpHandler trait --- sp-sim/src/gimlet.rs | 13 ----- sp-sim/src/sidecar.rs | 120 +++++++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 73 deletions(-) diff --git a/sp-sim/src/gimlet.rs b/sp-sim/src/gimlet.rs index e2eee60ae67..f4ab4600dce 100644 --- a/sp-sim/src/gimlet.rs +++ b/sp-sim/src/gimlet.rs @@ -31,7 +31,6 @@ use gateway_messages::DumpTask; use gateway_messages::Header; use gateway_messages::MgsRequest; use gateway_messages::MgsResponse; -use gateway_messages::MonorailError; use gateway_messages::PowerStateTransition; use gateway_messages::RotBootInfo; use gateway_messages::RotRequest; @@ -41,8 +40,6 @@ use gateway_messages::SpError; use gateway_messages::SpPort; use gateway_messages::SpRequest; use gateway_messages::SpStateV2; -use gateway_messages::UnlockChallenge; -use gateway_messages::UnlockResponse; use gateway_messages::ignition::{self, LinkEvents}; use gateway_messages::sp_impl::Sender; use gateway_messages::sp_impl::SpHandler; @@ -1725,16 +1722,6 @@ impl SpHandler for Handler { fn get_host_flash_hash(&mut self, slot: u16) -> Result<[u8; 32], SpError> { self.update_state.get_host_flash_hash(slot) } - - fn unlock( - &mut self, - _vid: Self::VLanId, - _challenge: UnlockChallenge, - _response: UnlockResponse, - _time_sec: u32, - ) -> Result<(), MonorailError> { - Err(MonorailError::UnlockFailed) - } } impl SimSpHandler for Handler { diff --git a/sp-sim/src/sidecar.rs b/sp-sim/src/sidecar.rs index 012be15dca9..812f517a067 100644 --- a/sp-sim/src/sidecar.rs +++ b/sp-sim/src/sidecar.rs @@ -615,6 +615,66 @@ impl Handler { rot: Ok(rot_state_v2(self.update_state.rot_state())), } } + + /// Pretends to unlock the tech port if the challenge and response are compatible + fn unlock( + &mut self, + _vid: ::VLanId, + challenge: UnlockChallenge, + response: UnlockResponse, + time_sec: u32, + ) -> Result<(), MonorailError> { + if time_sec > MAX_UNLOCK_TIME_SECS { + warn!(&self.log, "unlock time too long"; "time_sec" => time_sec); + return Err(MonorailError::TimeIsTooLong); + } + + // Callers only get one attempt per challenge; if they fail to authorize + // the unlock, they have to ask for a new challenge. + let Some((last_challenge, challenge_time)) = self.last_challenge.take() + else { + warn!(&self.log, "no challenge for monorail unlock"); + return Err(MonorailError::UnlockAuthFailed); + }; + + if challenge != last_challenge { + warn!(&self.log, "wrong challenge for monorail unlock"); + return Err(MonorailError::UnlockAuthFailed); + } + + if now() >= challenge_time + CHALLENGE_EXPIRATION_TIME_SECS { + warn!(&self.log, "challenge expired"); + return Err(MonorailError::UnlockAuthFailed); + } + + // Check that the response is valid for our current challenge + // and trusted keys. + match (challenge, response) { + ( + UnlockChallenge::Trivial { timestamp: ts1 }, + UnlockResponse::Trivial { timestamp: ts2 }, + ) if ts1 == ts2 => Ok(()), + ( + UnlockChallenge::EcdsaSha2Nistp256(data), + UnlockResponse::EcdsaSha2Nistp256 { + key, + signer_nonce, + signature, + }, + ) => verify_signature( + &self.log, + &self.trusted_keys, + &data, + &key, + &signer_nonce, + &signature, + ), + _ => Err(MonorailError::UnlockAuthFailed), + }?; + debug!(&self.log, "unlock auth succeeded"); + + Ok(()) + } } impl SpHandler for Handler { @@ -1140,66 +1200,6 @@ impl SpHandler for Handler { } } - /// Pretends to unlock the tech port if the challenge and response are compatible - fn unlock( - &mut self, - _vid: Self::VLanId, - challenge: UnlockChallenge, - response: UnlockResponse, - time_sec: u32, - ) -> Result<(), MonorailError> { - if time_sec > MAX_UNLOCK_TIME_SECS { - warn!(&self.log, "unlock time too long"; "time_sec" => time_sec); - return Err(MonorailError::TimeIsTooLong); - } - - // Callers only get one attempt per challenge; if they fail to authorize - // the unlock, they have to ask for a new challenge. - let Some((last_challenge, challenge_time)) = self.last_challenge.take() - else { - warn!(&self.log, "no challenge for monorail unlock"); - return Err(MonorailError::UnlockAuthFailed); - }; - - if challenge != last_challenge { - warn!(&self.log, "wrong challenge for monorail unlock"); - return Err(MonorailError::UnlockAuthFailed); - } - - if now() >= challenge_time + CHALLENGE_EXPIRATION_TIME_SECS { - warn!(&self.log, "challenge expired"); - return Err(MonorailError::UnlockAuthFailed); - } - - // Check that the response is valid for our current challenge - // and trusted keys. - match (challenge, response) { - ( - UnlockChallenge::Trivial { timestamp: ts1 }, - UnlockResponse::Trivial { timestamp: ts2 }, - ) if ts1 == ts2 => Ok(()), - ( - UnlockChallenge::EcdsaSha2Nistp256(data), - UnlockResponse::EcdsaSha2Nistp256 { - key, - signer_nonce, - signature, - }, - ) => verify_signature( - &self.log, - &self.trusted_keys, - &data, - &key, - &signer_nonce, - &signature, - ), - _ => Err(MonorailError::UnlockAuthFailed), - }?; - debug!(&self.log, "unlock auth succeeded"); - - Ok(()) - } - fn get_startup_options(&mut self) -> Result { warn!( &self.log, "asked for startup options (unsupported by sidecar)";