From b134a4501ef3e8d3b6f70115b734b3a3ff32e579 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 21 Oct 2024 12:03:28 +0200 Subject: [PATCH] Remove usbip example Maintaining a working usbip runner using admin-app and fido-authenticator requires lots of effort and code duplication and is out of scope for this repository. Instead, the usbip runner in the nitrokey-3-firmware repository can be used for development and testing. --- Cargo.toml | 43 +---- Makefile | 7 +- examples/Makefile | 97 ---------- examples/usbip.rs | 473 ---------------------------------------------- 4 files changed, 6 insertions(+), 614 deletions(-) delete mode 100644 examples/Makefile delete mode 100644 examples/usbip.rs diff --git a/Cargo.toml b/Cargo.toml index 51b9af59a..2ab256a07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,22 +26,6 @@ bitflags = "2.3.1" # extension trussed-auth = "0.3.0" -[dev-dependencies] -log = { version = "0.4.14", default-features = false } -pretty_env_logger = "0.4.0" - -# below are for running the usbip example -trussed-usbip = { version = "0.0.1", features = ["ctaphid"], default-features = false } -usbd-ctaphid = "0.1" -clap = { version = "3.0.0", features = ["cargo", "derive"] } -clap-num = "1.0.0" -delog = { version = "0.1.6", features = ["std-log"] } -fido-authenticator = { version = "0.1.1", features = ["dispatch", "log-all"]} -trussed-staging = { version = "0.3.0", features = ["hkdf"] } -trussed-hkdf = "0.2.0" - -admin-app = { version = "0.1", features = ["log-all"] } - [features] default = ["apdu-dispatch"] devel = ["apdu-dispatch", "log-all", "delog/std-log", "devel-counters"] @@ -58,7 +42,6 @@ calculate-all = [] # Require delay after failed request as a brute-force protection for Reverese HOTP Verification brute-force-delay = [] - log-all = [] log-none = [] log-info = [] @@ -66,30 +49,10 @@ log-debug = [] log-warn = [] log-error = [] -[[example]] -name="usbip" -required-features = ["ctaphid", "devel"] - - [patch.crates-io] -littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27e49ca321089d01d8c9b169c4aeb58ceeeca" } -flexiber = { git = "https://github.com/Nitrokey/flexiber", tag = "0.1.1.nitrokey" } apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" } - -# forked -admin-app = { git = "https://github.com/Nitrokey/admin-app", rev = "6c88a4bd58f2b6516c424b4dbf9581989ffa915e" } -ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "4846817d9cd44604121680a19d46f3264973a3ce" } -fido-authenticator = { git = "https://github.com/Nitrokey/fido-authenticator.git", tag = "v0.1.1-nitrokey.14" } -trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey.18" } - -# unreleased upstream changes -usbd-ctaphid = { git = "https://github.com/Nitrokey/usbd-ctaphid", tag = "v0.1.0-nitrokey.1" } ctaphid-dispatch = { git = "https://github.com/Nitrokey/ctaphid-dispatch", tag = "v0.1.1-nitrokey.2" } -serde-indexed = { git = "https://github.com/nitrokey/serde-indexed.git", tag = "v0.1.0-nitrokey.2" } - -# unreleased crates +flexiber = { git = "https://github.com/Nitrokey/flexiber", tag = "0.1.1.nitrokey" } +littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27e49ca321089d01d8c9b169c4aeb58ceeeca" } +trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey.18" } trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth", tag = "v0.3.0" } -trussed-usbip = { git = "https://github.com/Nitrokey/pc-usbip-runner", tag = "v0.0.1-nitrokey.1" } -trussed-hkdf = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "hkdf-v0.2.0" } -trussed-chunked = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "chunked-v0.1.0" } -trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.3.0" } diff --git a/Makefile b/Makefile index 789775d8f..4e06941d7 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,12 @@ .PHONY: ci setup-ubuntu run -FLAGS=--example usbip --features "ctaphid devel" - ci: + cargo check --all-targets + cargo check --all-targets --features apdu-dispatch,ctaphid cargo test --verbose - cargo build $(FLAGS) run: env RUST_LOG=debug cargo run $(FLAGS) setup-ubuntu: - sudo apt install llvm libclang-dev make \ No newline at end of file + sudo apt install llvm libclang-dev make diff --git a/examples/Makefile b/examples/Makefile deleted file mode 100644 index 10b769c85..000000000 --- a/examples/Makefile +++ /dev/null @@ -1,97 +0,0 @@ -APPNAME=udp_sim -FLAGS=--features=enable-logs - -all: | start-sim attach finish-message - -.PHONY: finish-message -finish-message: - @echo "###################################################" - @echo "Done. Device should be visible in your system now. Run 'make stop' to disconnect it." - -.PHONY: start-sim -start-sim: $(APPNAME) - -$(MAKE) stop - env RUST_LOG=info RUST_BACKTRACE=1 cargo run $(FLAGS) & - sleep 1 - -.PHONY: autoattach -autoattach: - while true; do $(MAKE) attach; sleep 1; inotifywait ${CARGO_TARGET_DIR}/debug/usbip-simulation; sleep 5; done; - -.PHONY: attach -attach: - lsmod | grep vhci-hcd || sudo modprobe vhci-hcd - sudo usbip list -r "localhost" - sudo usbip attach -r "localhost" -b "1-1" - sudo usbip attach -r "localhost" -b "1-1" - sleep 5 - -notify-send 'Webcrypt USB/IP' 'Attached' - - -.PHONY: ci -ci: - timeout 10 -k 5 $(MAKE) - -.PHONY: build -build: $(APPNAME) - -.PHONY: build-clean -build-clean: | clean build - -.PHONY: $(APPNAME) -$(APPNAME): - cargo build $(FLAGS) - -.PHONY: stop -stop: - -sudo usbip detach -p "00" - killall $(APPNAME) usbip-simulation - -.PHONY: setup-fedora -setup-fedora: - sudo dnf install usbip -y - sudo dnf install fuse-devel -y - sudo dnf install clang-libs clang -y - sudo ln -s /usr/lib64/libclang.so.15 /usr/lib64/libclang.so - -.PHONY: clean -clean: - cargo clean - rm $(APPNAME) -v - -.PHONY: build-docker -CMD=make -C /app/runners/pc-usbip/ build -build-docker: - docker build -t usbip . - mkdir -p cargo-cache - docker run -it --rm -v $(PWD)/cargo-cache:/root/.cargo -v $(PWD)/../../:/app usbip $(CMD) - touch $(APPNAME) - - -LFS=./tmp/littlefs-fuse/lfs -$(LFS): - mkdir -p tmp - -cd tmp && git clone https://github.com/littlefs-project/littlefs-fuse - cd tmp/littlefs-fuse && make - -fs=./trussed-state.bin -mount=/tmp/mnt/bee -state=mount.state - -.PHONY: mount umount -mount: $(LFS) - # https://github.com/littlefs-project/littlefs-fuse#usage-on-linux - mkdir -p $(mount) - lsmod | grep loop || sudo modprobe loop - sudo losetup --find --show $(fs) | tee loop.dev - grep "/dev/loop" loop.dev && sudo chmod a+rw `cat loop.dev` - $(LFS) --block_count=128 `cat loop.dev` $(mount) - printf "loop_device=`cat loop.dev`\nmount=$(mount)" > $(state) - tree -C -h $(mount) - -umount: - -umount $(mount) - sudo losetup -d `cat loop.dev` - rm ./$(state) - - diff --git a/examples/usbip.rs b/examples/usbip.rs deleted file mode 100644 index 67e304618..000000000 --- a/examples/usbip.rs +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright (C) 2023 Nitrokey GmbH -// -// SPDX-License-Identifier: Apache-2.0 OR MIT - -/// Taken from: https://github.com/Nitrokey/nitrokey-3-firmware/tree/main/runners/usbip -use std::path::{Path, PathBuf}; - -mod dispatch { - - use trussed::{ - api::{reply, request, Reply, Request}, - backend::{Backend as _, BackendId}, - error::Error, - platform::Platform, - serde_extensions::{ExtensionDispatch, ExtensionId, ExtensionImpl as _}, - service::ServiceResources, - types::{Bytes, Context, Location}, - }; - use trussed_auth::{AuthBackend, AuthContext, AuthExtension, MAX_HW_KEY_LEN}; - use trussed_hkdf::HkdfExtension; - use trussed_staging::{StagingBackend, StagingContext}; - - pub const BACKENDS: &[BackendId] = - &[BackendId::Custom(Backend::Auth), BackendId::Core]; - - pub enum Backend { - Auth, - Staging, - } - - pub enum Extension { - Auth, - Hkdf, - } - - impl From for u8 { - fn from(extension: Extension) -> Self { - match extension { - Extension::Auth => 0, - Extension::Hkdf => 1, - } - } - } - - impl TryFrom for Extension { - type Error = Error; - - fn try_from(id: u8) -> Result { - match id { - 0 => Ok(Extension::Auth), - 1 => Ok(Extension::Hkdf), - _ => Err(Error::InternalError), - } - } - } - - pub struct Dispatch { - auth: AuthBackend, - staging: StagingBackend, - } - - #[derive(Default)] - pub struct DispatchContext { - auth: AuthContext, - staging: StagingContext, - } - - impl Dispatch { - pub fn new() -> Self { - Self { - auth: AuthBackend::new(Location::Internal), - staging: StagingBackend::new(), - } - } - - pub fn with_hw_key(hw_key: Bytes) -> Self { - Self { - auth: AuthBackend::with_hw_key(Location::Internal, hw_key), - staging: StagingBackend::new(), - } - } - } - - impl ExtensionDispatch for Dispatch { - type BackendId = Backend; - type Context = DispatchContext; - type ExtensionId = Extension; - - fn core_request( - &mut self, - backend: &Self::BackendId, - ctx: &mut Context, - request: &Request, - resources: &mut ServiceResources

, - ) -> Result { - match backend { - Backend::Auth => { - self.auth - .request(&mut ctx.core, &mut ctx.backends.auth, request, resources) - } - Backend::Staging => self.staging.request( - &mut ctx.core, - &mut ctx.backends.staging, - request, - resources, - ), - } - } - - fn extension_request( - &mut self, - backend: &Self::BackendId, - extension: &Self::ExtensionId, - ctx: &mut Context, - request: &request::SerdeExtension, - resources: &mut ServiceResources

, - ) -> Result { - match backend { - Backend::Auth => match extension { - Extension::Auth => self.auth.extension_request_serialized( - &mut ctx.core, - &mut ctx.backends.auth, - request, - resources, - ), - _ => Err(Error::RequestNotAvailable), - }, - Backend::Staging => match extension { - Extension::Hkdf => self.staging.extension_request_serialized( - &mut ctx.core, - &mut ctx.backends.staging, - request, - resources, - ), - _ => Err(Error::RequestNotAvailable), - }, - } - } - } - - impl ExtensionId for Dispatch { - type Id = Extension; - - const ID: Self::Id = Self::Id::Auth; - } - impl ExtensionId for Dispatch { - type Id = Extension; - - const ID: Self::Id = Self::Id::Hkdf; - } -} - -#[cfg(feature = "ccid")] -use apdu_dispatch::command::SIZE as ApduCommandSize; - -use clap::Parser; -use clap_num::maybe_hex; -use log::{debug, info, warn}; -use trussed::backend::BackendId; -use trussed::platform::{consent, reboot, ui}; -use trussed::service::ClientFilestore; -use trussed::types::Location; -use trussed::virt::{self, Filesystem, StoreProvider}; -use trussed::{ClientImplementation, Platform}; -use trussed_usbip::ClientBuilder; - -use usbd_ctaphid::constants::MESSAGE_SIZE; - -pub type FidoConfig = fido_authenticator::Config; -pub type VirtClient = ClientImplementation< - trussed_usbip::Service, - dispatch::Dispatch, ->; - -/// USP/IP based virtualization of the Nitrokey 3 / Solo2 device. -#[derive(Parser, Debug)] -#[clap(about, version, author)] -struct Args { - /// USB Name string - #[clap(short, long, default_value = "Secrets App")] - name: String, - - /// USB Manufacturer string - #[clap(short, long, default_value = "Simulation")] - manufacturer: String, - - /// USB Serial string - #[clap(long, default_value = "SIM SIM SIM")] - serial: String, - - /// Trussed state file - #[clap(long, default_value = "trussed-state.bin")] - state_file: PathBuf, - - /// FIDO attestation key - #[clap(long)] - fido_key: Option, - - /// FIDO attestation cert - #[clap(long)] - fido_cert: Option, - - /// USB VID id - #[clap(short, long, parse(try_from_str=maybe_hex), default_value_t = 0x20a0)] - vid: u16, - /// USB PID id - #[clap(short, long, parse(try_from_str=maybe_hex), default_value_t = 0x42b2)] - pid: u16, -} - -struct Reboot; - -impl admin_app::Reboot for Reboot { - fn reboot() -> ! { - unimplemented!(); - } - - fn reboot_to_firmware_update() -> ! { - unimplemented!(); - } - - fn reboot_to_firmware_update_destructive() -> ! { - unimplemented!(); - } - - fn locked() -> bool { - false - } -} - -#[repr(u8)] -#[derive(Debug)] -pub enum CustomStatus { - ReverseHotpSuccess = 0, - ReverseHotpError = 1, - Unknown = 0xFF, -} - -impl From for u8 { - fn from(status: CustomStatus) -> Self { - status as _ - } -} - -impl TryFrom for CustomStatus { - type Error = UnknownStatusError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::ReverseHotpSuccess), - 1 => Ok(Self::ReverseHotpError), - _ => Err(UnknownStatusError(value)), - } - } -} - -pub struct UnknownStatusError(u8); - -impl CustomStatus {} - -#[derive(Debug)] -struct UserInterface { - start_time: std::time::Instant, - status: Option, -} - -impl UserInterface { - fn new() -> Self { - Self { - start_time: std::time::Instant::now(), - status: None, - } - } -} - -impl trussed::platform::UserInterface for UserInterface { - /// Prompt user to type a word for confirmation - fn check_user_presence(&mut self) -> consent::Level { - // use std::io::Read as _; - // This is not nice - we should "peek" and return Level::None - // if there is no key pressed yet (unbuffered read from stdin). - // Couldn't get this to work (without pulling in ncurses or similar). - // std::io::stdin().bytes().next(); - consent::Level::Normal - } - - fn set_status(&mut self, status: ui::Status) { - debug!("Set status: {:?}", status); - if let ui::Status::Custom(s) = status { - let cs: CustomStatus = CustomStatus::try_from(s).unwrap_or_else(|_| { - warn!("Unsupported status value: {:?}", status); - CustomStatus::Unknown - }); - info!("Set status: [{}] {:?}", s, cs); - } - - if status == ui::Status::WaitingForUserPresence { - info!(">>>> Received confirmation request. Confirming automatically."); - } - self.status = Some(status); - } - - fn refresh(&mut self) { - info!("Current status is: {:?}", self); - } - - fn uptime(&mut self) -> core::time::Duration { - self.start_time.elapsed() - } - - fn reboot(&mut self, to: reboot::To) -> ! { - info!("Restart! ({:?})", to); - std::process::exit(25); - } -} - -#[derive(Copy, Clone)] -pub enum Variant { - Usbip, - Lpc55, - Nrf52, -} -impl From for u8 { - fn from(variant: Variant) -> Self { - match variant { - Variant::Usbip => 0, - Variant::Lpc55 => 1, - Variant::Nrf52 => 2, - } - } -} - -pub struct AdminData { - pub init_status: u8, - pub ifs_blocks: u8, - pub efs_blocks: u16, - pub variant: Variant, -} -impl AdminData { - pub fn new(variant: Variant) -> Self { - Self { - init_status: 0, - ifs_blocks: u8::MAX, - efs_blocks: u16::MAX, - variant, - } - } -} - -pub type AdminStatus = [u8; 5]; -impl AdminData { - fn encode(&self) -> AdminStatus { - let efs_blocks = self.efs_blocks.to_be_bytes(); - [ - self.init_status, - self.ifs_blocks, - efs_blocks[0], - efs_blocks[1], - self.variant.into(), - ] - } -} - -struct Apps { - fido: fido_authenticator::Authenticator, - admin: admin_app::App, - secrets: secrets_app::Authenticator, -} - -const MAX_RESIDENT_CREDENTIAL_COUNT: u32 = 50; - -impl trussed_usbip::Apps<'static, VirtClient, dispatch::Dispatch> for Apps { - type Data = (); - fn new>(builder: &B, _data: ()) -> Self { - let fido = fido_authenticator::Authenticator::new( - builder.build("fido", &[BackendId::Core]), - fido_authenticator::Conforming {}, - fido_authenticator::Config { - max_msg_size: MESSAGE_SIZE, - skip_up_timeout: None, - max_resident_credential_count: Some(MAX_RESIDENT_CREDENTIAL_COUNT), - large_blobs: None, - nfc_transport: false, - }, - ); - - let mut filestore = ClientFilestore::new("admin".into(), unsafe { Filesystem::store() }); - let data = AdminData::new(Variant::Usbip); - let admin = admin_app::App::load( - builder.build("admin", &[BackendId::Core]), - &mut filestore, - [0; 16], - 0, - "", - data.encode(), - ); - let options = secrets_app::Options::new( - Location::External, - CustomStatus::ReverseHotpSuccess as u8, - CustomStatus::ReverseHotpError as u8, - [0x42, 0x42, 0x42, 0x42], - u16::MAX, - ); - let secrets = - secrets_app::Authenticator::new(builder.build("secrets", dispatch::BACKENDS), options); - - Self { - fido, - admin, - secrets, - } - } - - fn with_ctaphid_apps( - &mut self, - f: impl FnOnce(&mut [&mut dyn ctaphid_dispatch::app::App<'static>]) -> T, - ) -> T { - f(&mut [&mut self.fido, &mut self.admin, &mut self.secrets]) - } - - #[cfg(feature = "ccid")] - fn with_ccid_apps( - &mut self, - f: impl FnOnce(&mut [&mut dyn apdu_dispatch::app::App]) -> T, - ) -> T { - f(&mut []) - } -} - -fn main() { - pretty_env_logger::init(); - - let args = Args::parse(); - - let store = virt::Filesystem::new(args.state_file); - let options = trussed_usbip::Options { - manufacturer: Some(args.manufacturer), - product: Some(args.name), - serial_number: Some(args.serial), - vid: args.vid, - pid: args.pid, - }; - - info!("Initializing Trussed"); - trussed_usbip::Builder::new(store, options) - .dispatch(dispatch::Dispatch::new()) - .init_platform(move |platform| { - let ui: Box = - Box::new(UserInterface::new()); - platform.user_interface().set_inner(ui); - - if let Some(fido_key) = &args.fido_key { - store_file(platform, fido_key, "fido/sec/00"); - } - if let Some(fido_cert) = &args.fido_cert { - store_file(platform, fido_cert, "fido/x5c/00"); - } - }) - .build::() - .exec(|_| ()); -} - -fn store_file(platform: &impl Platform, host_file: &Path, device_file: &str) { - info!("Writing {} to file system", device_file); - let data = std::fs::read(host_file).expect("failed to read file"); - trussed::store::store( - platform.store(), - Location::Internal, - &trussed::types::PathBuf::from(device_file), - &data, - ) - .expect("failed to store file"); -}