Skip to content
This repository was archived by the owner on Mar 18, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ log-warn = []
log-error = []

[patch.crates-io]
# trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey-4"}
trussed = { git = "https://github.com/trussed-dev/trussed", rev = "d9276a689d68ffeb4c5d9ac635b18232be172f45"}
# littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-1" }
trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey.8"}
littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-2" }

[profile.dev.package.rsa]
opt-level = 2
Expand Down
2 changes: 1 addition & 1 deletion examples/usbip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl trussed_usbip::Apps<ClientImplementation<Syscall<virt::Platform<Ram>>>, ()>
_data: (),
) -> Self {
PivApp {
piv: piv::Authenticator::new(make_client("piv")),
piv: piv::Authenticator::new(make_client("piv"), piv::Options::default()),
}
}

Expand Down
4 changes: 3 additions & 1 deletion examples/virtual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@

// TODO: add CLI

use piv_authenticator::{Authenticator, Options};

fn main() {
env_logger::init();

trussed_rsa_alloc::virt::with_ram_client("piv-authenticator", |client| {
let card = piv_authenticator::Authenticator::new(client);
let card = Authenticator::new(client, Options::default());
let mut virtual_card = piv_authenticator::vpicc::VirtualCard::new(card);
let vpicc = vpicc::connect().expect("failed to connect to vpicc");
vpicc
Expand Down
62 changes: 34 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use core::convert::TryInto;
use flexiber::EncodableHeapless;
use heapless_bytes::Bytes;
use iso7816::{Data, Status};
use trussed::types::{KeySerialization, Location, StorageAttributes};
use trussed::types::{KeySerialization, Location, PathBuf, StorageAttributes};
use trussed::{client, syscall, try_syscall};

use constants::*;
Expand All @@ -45,17 +45,38 @@ pub type Result<O = ()> = iso7816::Result<O>;
use reply::Reply;
use state::{AdministrationAlgorithm, CommandCache, KeyWithAlg, LoadedState, State, TouchPolicy};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Options {
storage: Location,
}

impl Default for Options {
fn default() -> Self {
Self {
storage: Location::External,
}
}
}

impl Options {
pub fn storage(self, storage: Location) -> Self {
Self { storage, ..self }
}
}

/// PIV authenticator Trussed app.
///
/// The `C` parameter is necessary, as PIV includes command sequences,
/// where we need to store the previous command, so we need to know how
/// much space to allocate.
pub struct Authenticator<T> {
options: Options,
state: State,
trussed: T,
}

struct LoadedAuthenticator<'a, T> {
options: &'a mut Options,
state: LoadedState<'a>,
trussed: &'a mut T,
}
Expand All @@ -70,21 +91,23 @@ impl<T> Authenticator<T>
where
T: client::Client + client::Ed255 + client::Tdes,
{
pub fn new(trussed: T) -> Self {
pub fn new(trussed: T, options: Options) -> Self {
// seems like RefCell is not the right thing, we want something like `Rc` instead,
// which can be cloned and injected into other parts of the App that use Trussed.
// let trussed = RefCell::new(trussed);
Self {
// state: state::State::new(trussed.clone()),
state: Default::default(),
options,
trussed,
}
}

fn load(&mut self) -> core::result::Result<LoadedAuthenticator<'_, T>, Status> {
Ok(LoadedAuthenticator {
state: self.state.load(&mut self.trussed)?,
state: self.state.load(&mut self.trussed, self.options.storage)?,
trussed: &mut self.trussed,
options: &mut self.options,
})
}

Expand Down Expand Up @@ -170,30 +193,13 @@ where
}

YubicoPivExtension::Reset => {
let persistent_state = self.state.persistent(&mut self.trussed)?;
let this = self.load()?;

// TODO: find out what all needs resetting :)
persistent_state.reset_pin(&mut self.trussed);
persistent_state.reset_puk(&mut self.trussed);
persistent_state.reset_administration_key(&mut self.trussed);
self.state.volatile.app_security_status.pin_verified = false;
self.state.volatile.app_security_status.puk_verified = false;
self.state
.volatile
.app_security_status
.administrator_verified = false;

try_syscall!(self.trussed.remove_file(
trussed::types::Location::Internal,
trussed::types::PathBuf::from(b"printed-information"),
))
.ok();

try_syscall!(self.trussed.remove_file(
trussed::types::Location::Internal,
trussed::types::PathBuf::from(b"authentication-key.x5c"),
))
.ok();
for location in [Location::Volatile, Location::External, Location::Internal] {
try_syscall!(this.trussed.delete_all(location)).ok();
try_syscall!(this.trussed.remove_dir_all(location, PathBuf::new())).ok();
}
}

YubicoPivExtension::SetManagementKey(touch_policy) => {
Expand Down Expand Up @@ -916,7 +922,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T>
let offset = reply.len();
match container {
Container::KeyHistoryObject => self.get_key_history_object(reply.lend())?,
_ => match ContainerStorage(container).load(self.trussed)? {
_ => match ContainerStorage(container).load(self.trussed, self.options.storage)? {
Some(data) => reply.expand(&data)?,
None => return Err(Status::NotFound),
},
Expand Down Expand Up @@ -946,7 +952,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T>
};

use state::ContainerStorage;
ContainerStorage(container).save(self.trussed, data)
ContainerStorage(container).save(self.trussed, data, self.options.storage)
}

fn reset_retry_counter(&mut self, data: ResetRetryCounter) -> Result {
Expand Down Expand Up @@ -976,7 +982,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T>
use state::ContainerStorage;

for c in RETIRED_CERTS {
if ContainerStorage(c).exists(self.trussed)? {
if ContainerStorage(c).exists(self.trussed, self.options.storage)? {
num_certs += 1;
}
}
Expand Down
78 changes: 50 additions & 28 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,13 @@ pub struct State {
}

impl State {
pub fn load(&mut self, client: &mut impl trussed::Client) -> Result<LoadedState<'_>, Status> {
pub fn load(
&mut self,
client: &mut impl trussed::Client,
storage: Location,
) -> Result<LoadedState<'_>, Status> {
if self.persistent.is_none() {
self.persistent = Some(Persistent::load_or_initialize(client)?);
self.persistent = Some(Persistent::load_or_initialize(client, storage)?);
}
Ok(LoadedState {
volatile: &mut self.volatile,
Expand All @@ -201,8 +205,9 @@ impl State {
pub fn persistent(
&mut self,
client: &mut impl trussed::Client,
storage: Location,
) -> Result<&mut Persistent, Status> {
Ok(self.load(client)?.persistent)
Ok(self.load(client, storage)?.persistent)
}

pub fn new() -> Self {
Expand All @@ -216,6 +221,11 @@ pub struct LoadedState<'t> {
pub persistent: &'t mut Persistent,
}

/// exists only to please serde, which doesn't accept enum variants in `#[serde(default=…)]`
fn volatile() -> Location {
Location::Volatile
}

#[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Persistent {
pub keys: Keys,
Expand All @@ -230,6 +240,8 @@ pub struct Persistent {
// pin_hash: Option<[u8; 16]>,
// Ideally, we'd dogfood a "Monotonic Counter" from `trussed`.
timestamp: u32,
#[serde(skip, default = "volatile")]
storage: Location,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
Expand Down Expand Up @@ -443,7 +455,7 @@ impl Persistent {
let id = syscall!(client.unsafe_inject_key(
alg.mechanism(),
management_key,
trussed::types::Location::Internal,
self.storage,
KeySerialization::Raw
))
.key;
Expand Down Expand Up @@ -471,7 +483,7 @@ impl Persistent {
) -> KeyId {
let id = syscall!(client.generate_key(
alg.key_mechanism(),
StorageAttributes::default().set_persistence(Location::Internal)
StorageAttributes::default().set_persistence(self.storage)
))
.key;
let old = self.set_asymmetric_key(key, id, alg);
Expand All @@ -482,13 +494,13 @@ impl Persistent {
id
}

pub fn initialize(client: &mut impl trussed::Client) -> Self {
pub fn initialize(client: &mut impl trussed::Client, storage: Location) -> Self {
info!("initializing PIV state");
let administration = KeyWithAlg {
id: syscall!(client.unsafe_inject_key(
YUBICO_DEFAULT_MANAGEMENT_KEY_ALG.mechanism(),
YUBICO_DEFAULT_MANAGEMENT_KEY,
trussed::types::Location::Internal,
storage,
KeySerialization::Raw
))
.key,
Expand All @@ -498,7 +510,7 @@ impl Persistent {
let authentication = KeyWithAlg {
id: syscall!(client.generate_key(
Mechanism::P256,
StorageAttributes::new().set_persistence(Location::Internal)
StorageAttributes::new().set_persistence(storage)
))
.key,
alg: AsymmetricAlgorithms::P256,
Expand All @@ -521,6 +533,7 @@ impl Persistent {
.save(
client,
&guid_file[2..], // Remove the unnecessary 53 tag
storage,
)
.ok();

Expand All @@ -541,34 +554,35 @@ impl Persistent {
pin: Pin::try_from(Self::DEFAULT_PIN).unwrap(),
puk: Puk::try_from(Self::DEFAULT_PUK).unwrap(),
timestamp: 0,
// In case of forgotten to rebind, ensure the bug is found
storage: Location::Volatile,
};
state.save(client);
state
}

pub fn load_or_initialize(client: &mut impl trussed::Client) -> Result<Self, Status> {
pub fn load_or_initialize(
client: &mut impl trussed::Client,
storage: Location,
) -> Result<Self, Status> {
// todo: can't seem to combine load + initialize without code repetition
let data = load_if_exists(client, Location::Internal, &PathBuf::from(Self::FILENAME))?;
let data = load_if_exists(client, storage, &PathBuf::from(Self::FILENAME))?;
let Some(bytes) = data else {
return Ok( Self::initialize(client));
return Ok( Self::initialize(client, storage));
};

let parsed = trussed::cbor_deserialize(&bytes).map_err(|_err| {
let mut parsed: Self = trussed::cbor_deserialize(&bytes).map_err(|_err| {
error!("{_err:?}");
Status::UnspecifiedPersistentExecutionError
})?;
parsed.storage = storage;
Ok(parsed)
}

pub fn save(&mut self, client: &mut impl trussed::Client) {
let data: trussed::types::Message = trussed::cbor_serialize_bytes(&self).unwrap();

syscall!(client.write_file(
Location::Internal,
PathBuf::from(Self::FILENAME),
data,
None,
));
syscall!(client.write_file(self.storage, PathBuf::from(Self::FILENAME), data, None,));
}

pub fn timestamp(&mut self, client: &mut impl trussed::Client) -> u32 {
Expand Down Expand Up @@ -661,8 +675,12 @@ impl ContainerStorage {
}
}

pub fn exists(self, client: &mut impl trussed::Client) -> Result<bool, Status> {
match try_syscall!(client.entry_metadata(Location::Internal, self.path())) {
pub fn exists(
self,
client: &mut impl trussed::Client,
storage: Location,
) -> Result<bool, Status> {
match try_syscall!(client.entry_metadata(storage, self.path())) {
Ok(Metadata { metadata: None }) => Ok(false),
Ok(Metadata {
metadata: Some(metadata),
Expand All @@ -686,22 +704,26 @@ impl ContainerStorage {
pub fn load(
self,
client: &mut impl trussed::Client,
storage: Location,
) -> Result<Option<Bytes<MAX_MESSAGE_LENGTH>>, Status> {
load_if_exists(client, Location::Internal, &self.path())
load_if_exists(client, storage, &self.path())
.map(|data| data.or_else(|| self.default().map(Bytes::from)))
}

pub fn save(self, client: &mut impl trussed::Client, bytes: &[u8]) -> Result<(), Status> {
pub fn save(
self,
client: &mut impl trussed::Client,
bytes: &[u8],
storage: Location,
) -> Result<(), Status> {
let msg = Bytes::from(heapless::Vec::try_from(bytes).map_err(|_| {
error!("Buffer full");
Status::IncorrectDataParameter
})?);
try_syscall!(client.write_file(Location::Internal, self.path(), msg, None)).map_err(
|_err| {
error!("Failed to store data: {_err:?}");
Status::UnspecifiedNonpersistentExecutionError
},
)?;
try_syscall!(client.write_file(storage, self.path(), msg, None)).map_err(|_err| {
error!("Failed to store data: {_err:?}");
Status::UnspecifiedNonpersistentExecutionError
})?;
Ok(())
}
}
4 changes: 2 additions & 2 deletions tests/card/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2022 Nitrokey GmbH
// SPDX-License-Identifier: LGPL-3.0-only

use piv_authenticator::{vpicc::VirtualCard, Authenticator};
use piv_authenticator::{vpicc::VirtualCard, Authenticator, Options};

use std::{sync::mpsc, thread::sleep, time::Duration};
use stoppable_thread::spawn;
Expand All @@ -18,7 +18,7 @@ pub fn with_vsc<F: FnOnce() -> R, R>(f: F) -> R {
let (tx, rx) = mpsc::channel();
let handle = spawn(move |stopped| {
trussed_rsa_alloc::virt::with_ram_client("opcard", |client| {
let card = Authenticator::new(client);
let card = Authenticator::new(client, Options::default());
let mut virtual_card = VirtualCard::new(card);
let mut result = Ok(());
while !stopped.get() && result.is_ok() {
Expand Down
Loading