Replace FIDO example with a dummy app

Maintaining a working setup for the FIDO and admin app is a lot of work
and is redundant to the usbip runner provided as part of the Nitrokey 3
firmware.  Therefore, this patch replaces the fido example with a much
simpler example that just sets up a dummy client responding to a vendor
command that requests random data.

This is sufficient for basic testing and getting started.  For more
advanced use cases, the Nitrokey 3 runner should be used.

Fixes: https://github.com/trussed-dev/pc-usbip-runner/issues/31
This commit is contained in:
Robin Krahl
2024-06-06 15:04:20 +02:00
parent 7aed9ff347
commit ea45e12638
6 changed files with 155 additions and 266 deletions
Generated
+1 -52
View File
@@ -2,19 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "admin-app"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67d9f4831720ac3f95d708922b71cbec0d085548affb935f4af35395af28366c"
dependencies = [
"apdu-dispatch",
"ctaphid-dispatch",
"delog",
"iso7816",
"trussed",
]
[[package]]
name = "aead"
version = "0.5.2"
@@ -521,26 +508,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "fido-authenticator"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda469ecf5b58ba898e1a6e68f99529b568e24490f45ced8222240d10d7c6508"
dependencies = [
"apdu-dispatch",
"ctap-types",
"ctaphid-dispatch",
"delog",
"heapless",
"interchange 0.2.2",
"iso7816",
"littlefs2 0.3.2",
"serde",
"serde-indexed",
"serde_cbor",
"trussed",
]
[[package]]
name = "flexiber"
version = "0.1.0"
@@ -773,22 +740,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "littlefs2"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc089501e32d62b3e4d809a29b9e00d6211a197440602d69f304f1e4e82136b"
dependencies = [
"bitflags",
"cstr_core",
"cty",
"delog",
"generic-array",
"heapless",
"littlefs2-sys",
"serde",
]
[[package]]
name = "littlefs2"
version = "0.4.0"
@@ -1517,7 +1468,7 @@ dependencies = [
"hex-literal",
"hmac 0.12.1",
"interchange 0.3.0",
"littlefs2 0.4.0",
"littlefs2",
"nb 1.1.0",
"p256-cortex-m4",
"postcard",
@@ -1535,13 +1486,11 @@ dependencies = [
name = "trussed-usbip"
version = "0.0.1"
dependencies = [
"admin-app",
"apdu-dispatch",
"clap",
"clap-num",
"ctaphid-dispatch",
"delog",
"fido-authenticator",
"interchange 0.3.0",
"log",
"pretty_env_logger",
+1 -10
View File
@@ -24,21 +24,13 @@ clap = { version = "3.0.0", features = ["derive"] }
clap-num = "1.0.0"
delog = { version = "0.1.6", features = ["std-log"] }
pretty_env_logger = "0.4.0"
trussed = { version = "0.1", features = ["clients-3"] }
# applications
admin-app = { version = "0.1", features = ["log-all"] }
fido-authenticator = { version = "0.1", features = ["dispatch", "log-all"] }
trussed = { version = "0.1", features = ["clients-1"] }
[features]
default = ["ctaphid", "ccid"]
ctaphid = ["ctaphid-dispatch", "usbd-ctaphid"]
ccid = ["apdu-dispatch", "usbd-ccid"]
[[example]]
name = "fido"
required-features = ["ctaphid"]
[patch.crates-io]
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" }
@@ -46,4 +38,3 @@ usbd-ctaphid = { git = "https://github.com/trussed-dev/usbd-ctaphid", rev = "e9c
usbd-ccid = { git = "https://github.com/trussed-dev/usbd-ccid", tag = "0.3.0" }
ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" }
apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "b72d5eb9f4d7a3f107a78a2f0e41f3c403f4c7a4" }
+1 -1
View File
@@ -1,4 +1,4 @@
EXAMPLE_NAME := fido
EXAMPLE_NAME := dummy
all: | start-sim attach finish-message
+13 -6
View File
@@ -1,16 +1,23 @@
# USB/IP Simulation
# trussed-usbip
This runner allows using USB/IP as a means to simulate device connection
to the OS, and should allow faster development of the embedded applications.
This crate facilitates simulation of Trussed devices using USB/IP.
It should only be used for development and testing.
Remarks:
- Extensible with CTAP apps: currently FIDO and Admin are active;
- Does not work with Firefox at the moment;
- Allows to inject own FIDO certificates, and device properties;
- Requires multiple `usbip attach` calls to make it work [1].
- Works best with CTAPHID. CCID is supported but often unstable.
[1] https://github.com/Sawchord/usbip-device#known-bugs
## Examples
[`examples/dummy.rs`](`examples/dummy.rs`) contains a very simple example that
shows how to run a simulated Trussed device.
For a more complex example, see the [usbip runner][] of the Nitrokey 3 that
provides all features of the Nitrokey 3.
[usbip runner]: https://github.com/Nitrokey/nitrokey-3-firmware/tree/main/runners/usbip
## Setup
USB/IP tools are required to work, as well as kernel supporting it.
+139
View File
@@ -0,0 +1,139 @@
//! USB/IP simulation of a Trussed device.
//!
//! This example contains a dummy app that responds with random data to the CTAPHID vendor command
//! 0x60. It can be tested with `nitropy nk3 list` and `nitropy nk3 rng`.
//!
//! For a more complete example, see the [usbip runner][] for the Nitrokey 3.
//!
//! [usbip runner]: https://github.com/Nitrokey/nitrokey-3-firmware/tree/main/runners/usbip
use std::path::PathBuf;
#[cfg(feature = "ccid")]
use apdu_dispatch::command::SIZE as ApduCommandSize;
#[cfg(feature = "ctaphid")]
use ctaphid_dispatch::{
command::{Command, VendorCommand},
types::{AppResult, Error, Message},
};
use clap::Parser;
use clap_num::maybe_hex;
use trussed::{
backend::CoreOnly,
client::{Client, ClientBuilder},
service::Service,
syscall,
types::Vec,
virt::{self, Platform, StoreProvider},
};
use trussed_usbip::Syscall;
/// USP/IP based virtualization a Trussed device.
#[derive(Parser, Debug)]
#[clap(about, version, author)]
struct Args {
/// USB Name string
#[clap(short, long, default_value = "Trussed")]
name: String,
/// USB Manufacturer string
#[clap(short, long, default_value = "Trussed")]
manufacturer: String,
/// Trussed state file
#[clap(long, default_value = "trussed-state.bin")]
state_file: PathBuf,
/// 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 DummyApp<C: Client> {
client: C,
}
impl<C: Client> DummyApp<C> {
fn rng<const N: usize>(&mut self, response: &mut Vec<u8, N>) {
let bytes = syscall!(self.client.random_bytes(57)).bytes;
response.extend_from_slice(&bytes).unwrap();
}
}
#[cfg(feature = "ctaphid")]
const CTAPHID_COMMAND_RNG: Command = Command::Vendor(VendorCommand::H60);
#[cfg(feature = "ctaphid")]
impl<'a, C: Client> ctaphid_dispatch::app::App<'a> for DummyApp<C> {
fn commands(&self) -> &'static [Command] {
&[CTAPHID_COMMAND_RNG]
}
fn call(&mut self, command: Command, _request: &Message, response: &mut Message) -> AppResult {
match command {
CTAPHID_COMMAND_RNG => self.rng(response),
_ => return Err(Error::InvalidCommand),
}
Ok(())
}
}
struct Apps<C: Client> {
dummy: DummyApp<C>,
}
impl<'a, S: StoreProvider> trussed_usbip::Apps<'a, S, CoreOnly>
for Apps<trussed_usbip::Client<CoreOnly>>
{
type Data = ();
fn new(service: &mut Service<Platform<S>, CoreOnly>, syscall: Syscall, _data: ()) -> Self {
let client = ClientBuilder::new("dummy")
.prepare(service)
.unwrap()
.build(syscall);
let dummy = DummyApp { client };
Self { dummy }
}
#[cfg(feature = "ctaphid")]
fn with_ctaphid_apps<T>(
&mut self,
f: impl FnOnce(&mut [&mut dyn ctaphid_dispatch::app::App<'a>]) -> T,
) -> T {
f(&mut [&mut self.dummy])
}
#[cfg(feature = "ccid")]
fn with_ccid_apps<T>(
&mut self,
f: impl FnOnce(&mut [&mut dyn apdu_dispatch::app::App<ApduCommandSize, ApduCommandSize>]) -> 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: None,
vid: args.vid,
pid: args.pid,
};
log::info!("Initializing Trussed");
trussed_usbip::Builder::new(store, options)
.build::<Apps<_>>()
.exec(|_| ());
}
-197
View File
@@ -1,197 +0,0 @@
use std::path::{Path, PathBuf};
#[cfg(feature = "ccid")]
use apdu_dispatch::command::SIZE as ApduCommandSize;
use clap::Parser;
use clap_num::maybe_hex;
use log::info;
use trussed::platform::{consent, reboot, ui};
use trussed::{backend::Dispatch, virt, Client, Platform};
use trussed_usbip::ClientBuilder;
use fido_authenticator::TrussedRequirements;
use usbd_ctaphid::constants::MESSAGE_SIZE;
pub type FidoConfig = fido_authenticator::Config;
/// USP/IP based virtualization of the Nitrokey 3 / Solo2 device.
/// Supports FIDO application at the moment.
#[derive(Parser, Debug)]
#[clap(about, version, author)]
struct Args {
/// USB Name string
#[clap(short, long, default_value = "FIDO authenticator")]
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<PathBuf>,
/// FIDO attestation cert
#[clap(long)]
fido_cert: Option<PathBuf>,
/// 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
}
}
struct UserInterface {
start_time: std::time::Instant,
}
impl UserInterface {
fn new() -> Self {
Self {
start_time: std::time::Instant::now(),
}
}
}
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) {
info!("Set status: {:?}", status);
if status == ui::Status::WaitingForUserPresence {
info!(">>>> Received confirmation request. Confirming automatically.");
}
}
fn refresh(&mut 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);
}
}
struct Apps<C: Client + TrussedRequirements> {
fido: fido_authenticator::Authenticator<fido_authenticator::Conforming, C>,
admin: admin_app::App<C, Reboot>,
}
impl<C: Client + TrussedRequirements, D: Dispatch> trussed_usbip::Apps<C, D> for Apps<C> {
type Data = ();
fn new<B: ClientBuilder<C, D>>(builder: &B, _data: ()) -> Self {
let fido = fido_authenticator::Authenticator::new(
builder.build("fido", &[]),
fido_authenticator::Conforming {},
fido_authenticator::Config {
max_msg_size: MESSAGE_SIZE,
skip_up_timeout: None,
},
);
let admin = admin_app::App::new(builder.build("admin", &[]), [0; 16], 0);
Self { fido, admin }
}
fn with_ctaphid_apps<T>(
&mut self,
f: impl FnOnce(&mut [&mut dyn ctaphid_dispatch::app::App]) -> T,
) -> T {
f(&mut [&mut self.fido, &mut self.admin])
}
#[cfg(feature = "ccid")]
fn with_ccid_apps<T>(
&mut self,
f: impl FnOnce(&mut [&mut dyn apdu_dispatch::app::App<ApduCommandSize, ApduCommandSize>]) -> 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,
};
log::info!("Initializing Trussed");
trussed_usbip::Builder::new(store, options)
.init_platform(move |platform| {
let ui: Box<dyn trussed::platform::UserInterface + Send + Sync> =
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::<Apps<_>>()
.exec(|_| ());
}
fn store_file(platform: &impl Platform, host_file: &Path, device_file: &str) {
log::info!("Writing {} to file system", device_file);
let data = std::fs::read(host_file).expect("failed to read file");
trussed::store::store(
platform.store(),
trussed::types::Location::Internal,
&trussed::types::PathBuf::from(device_file),
&data,
)
.expect("failed to store file");
}