mirror of
https://github.com/trussed-dev/piv-authenticator.git
synced 2026-06-20 04:16:15 -07:00
+2
-3
@@ -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
|
||||
|
||||
+1
-1
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+34
-28
@@ -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::*;
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
@@ -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),
|
||||
},
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+50
-28
@@ -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,
|
||||
@@ -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 {
|
||||
@@ -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,
|
||||
@@ -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)]
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -521,6 +533,7 @@ impl Persistent {
|
||||
.save(
|
||||
client,
|
||||
&guid_file[2..], // Remove the unnecessary 53 tag
|
||||
storage,
|
||||
)
|
||||
.ok();
|
||||
|
||||
@@ -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 {
|
||||
@@ -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),
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
@@ -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() {
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ macro_rules! cmd {
|
||||
};
|
||||
}
|
||||
|
||||
use piv_authenticator::{Authenticator, Options};
|
||||
use trussed::virt::Ram;
|
||||
use trussed_rsa_alloc::virt::Client;
|
||||
|
||||
@@ -18,7 +19,7 @@ pub type Piv = piv_authenticator::Authenticator<Client<Ram>>;
|
||||
|
||||
pub fn piv<R>(test: impl FnOnce(&mut Piv) -> R) -> R {
|
||||
trussed_rsa_alloc::virt::with_ram_client("test", |client| {
|
||||
let mut piv_app = piv_authenticator::Authenticator::new(client);
|
||||
let mut piv_app = Authenticator::new(client, Options::default());
|
||||
test(&mut piv_app)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user