mirror of
https://github.com/trussed-dev/piv-authenticator.git
synced 2026-06-20 04:16:15 -07:00
Merge pull request #14 from Nitrokey/trussed-auth
Use trussed-auth for pin authentication
This commit is contained in:
+7
-4
@@ -17,7 +17,7 @@ required-features = ["vpicc"]
|
||||
|
||||
[[example]]
|
||||
name = "usbip"
|
||||
required-features = ["apdu-dispatch"]
|
||||
required-features = ["apdu-dispatch", "virt"]
|
||||
|
||||
[dependencies]
|
||||
apdu-dispatch = { version = "0.1", optional = true }
|
||||
@@ -29,6 +29,7 @@ interchange = "0.2.2"
|
||||
iso7816 = "0.1"
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
trussed = { version = "0.1" }
|
||||
trussed-auth = { version = "0.2" }
|
||||
untrusted = "0.9"
|
||||
vpicc = { version = "0.1.0", optional = true }
|
||||
log = "0.4"
|
||||
@@ -52,7 +53,7 @@ stoppable_thread = "0.2.1"
|
||||
expectrl = "0.6.0"
|
||||
|
||||
# Examples
|
||||
trussed-usbip = { git = "https://github.com/trussed-dev/pc-usbip-runner", default-features = false, features = ["ccid"], rev = "d2957b6c24c2b0cafbbfacd6fecd62c80943630b"}
|
||||
trussed-usbip = { git = "https://github.com/trussed-dev/pc-usbip-runner", default-features = false, features = ["ccid"], rev = "f3a680ca4c9a1411838ae0774f1713f79d4c2979"}
|
||||
usbd-ccid = { version = "0.2.0", features = ["highspeed-usb"]}
|
||||
rand = "0.8.5"
|
||||
|
||||
@@ -60,7 +61,8 @@ rand = "0.8.5"
|
||||
default = []
|
||||
strict-pin = []
|
||||
std = []
|
||||
vpicc = ["std", "dep:vpicc", "trussed-rsa-alloc/virt"]
|
||||
vpicc = ["std", "dep:vpicc", "virt"]
|
||||
virt = ["std"]
|
||||
pivy-tests = []
|
||||
opensc-tests = []
|
||||
|
||||
@@ -72,7 +74,8 @@ log-warn = []
|
||||
log-error = []
|
||||
|
||||
[patch.crates-io]
|
||||
trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey.8"}
|
||||
trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey.8"}
|
||||
trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth", tag = "v0.2.1"}
|
||||
littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-2" }
|
||||
|
||||
[profile.dev.package.rsa]
|
||||
|
||||
+20
-11
@@ -3,9 +3,15 @@
|
||||
|
||||
use trussed::virt::{self, Ram, UserInterface};
|
||||
use trussed::{ClientImplementation, Platform};
|
||||
use trussed_usbip::ClientBuilder;
|
||||
|
||||
use piv_authenticator as piv;
|
||||
use trussed_usbip::Syscall;
|
||||
use piv_authenticator::{
|
||||
self as piv,
|
||||
virt::dispatch::{self, Dispatch},
|
||||
};
|
||||
|
||||
type VirtClient =
|
||||
ClientImplementation<trussed_usbip::Service<Ram, dispatch::Dispatch>, dispatch::Dispatch>;
|
||||
|
||||
const MANUFACTURER: &str = "Nitrokey";
|
||||
const PRODUCT: &str = "Nitrokey 3";
|
||||
@@ -13,16 +19,17 @@ const VID: u16 = 0x20a0;
|
||||
const PID: u16 = 0x42b2;
|
||||
|
||||
struct PivApp {
|
||||
piv: piv::Authenticator<ClientImplementation<Syscall<virt::Platform<Ram>>>>,
|
||||
piv: piv::Authenticator<VirtClient>,
|
||||
}
|
||||
|
||||
impl trussed_usbip::Apps<ClientImplementation<Syscall<virt::Platform<Ram>>>, ()> for PivApp {
|
||||
fn new(
|
||||
make_client: impl Fn(&str) -> ClientImplementation<Syscall<virt::Platform<Ram>>>,
|
||||
_data: (),
|
||||
) -> Self {
|
||||
impl trussed_usbip::Apps<VirtClient, Dispatch> for PivApp {
|
||||
type Data = ();
|
||||
fn new<B: ClientBuilder<VirtClient, Dispatch>>(builder: &B, _data: ()) -> Self {
|
||||
PivApp {
|
||||
piv: piv::Authenticator::new(make_client("piv"), piv::Options::default()),
|
||||
piv: piv::Authenticator::new(
|
||||
builder.build("piv", dispatch::BACKENDS),
|
||||
piv::Options::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +51,13 @@ fn main() {
|
||||
vid: VID,
|
||||
pid: PID,
|
||||
};
|
||||
trussed_usbip::Runner::new(virt::Ram::default(), options)
|
||||
trussed_usbip::Builder::new(virt::Ram::default(), options)
|
||||
.dispatch(Dispatch::new())
|
||||
.init_platform(move |platform| {
|
||||
let ui: Box<dyn trussed::platform::UserInterface + Send + Sync> =
|
||||
Box::new(UserInterface::new());
|
||||
platform.user_interface().set_inner(ui);
|
||||
})
|
||||
.exec::<PivApp, _, _>(|_platform| {});
|
||||
.build::<PivApp>()
|
||||
.exec(|_platform| {});
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,12 +11,12 @@
|
||||
|
||||
// TODO: add CLI
|
||||
|
||||
use piv_authenticator::{Authenticator, Options};
|
||||
use piv_authenticator::{virt::with_ram_client, Authenticator, Options};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
trussed_rsa_alloc::virt::with_ram_client("piv-authenticator", |client| {
|
||||
with_ram_client("piv-authenticator", |client| {
|
||||
let card = Authenticator::new(client, Options::default());
|
||||
let mut vpicc_card = piv_authenticator::vpicc::VpiccCard::new(card);
|
||||
let vpicc = vpicc::connect().expect("failed to connect to vpicc");
|
||||
|
||||
+2
-1
@@ -5,11 +5,12 @@ use crate::{reply::Reply, Authenticator, /*constants::PIV_AID,*/ Result};
|
||||
|
||||
use apdu_dispatch::{app::App, command, response, Command};
|
||||
use trussed::client;
|
||||
use trussed_auth::AuthClient;
|
||||
|
||||
#[cfg(feature = "apdu-dispatch")]
|
||||
impl<T> App<{ command::SIZE }, { response::SIZE }> for Authenticator<T>
|
||||
where
|
||||
T: client::Client + client::Ed255 + client::Tdes,
|
||||
T: client::Client + AuthClient + client::Ed255 + client::Tdes,
|
||||
{
|
||||
fn select(&mut self, _apdu: &Command, reply: &mut response::Data) -> Result {
|
||||
self.select(Reply(reply))
|
||||
|
||||
+23
-49
@@ -26,6 +26,8 @@ mod tlv;
|
||||
|
||||
pub use piv_types::{AsymmetricAlgorithms, Pin, Puk};
|
||||
|
||||
#[cfg(feature = "virt")]
|
||||
pub mod virt;
|
||||
#[cfg(feature = "vpicc")]
|
||||
pub mod vpicc;
|
||||
|
||||
@@ -36,6 +38,7 @@ use heapless_bytes::Bytes;
|
||||
use iso7816::{Data, Status};
|
||||
use trussed::types::{KeySerialization, Location, PathBuf, StorageAttributes};
|
||||
use trussed::{client, syscall, try_syscall};
|
||||
use trussed_auth::AuthClient;
|
||||
|
||||
use constants::*;
|
||||
|
||||
@@ -62,7 +65,7 @@ impl Default for Options {
|
||||
|
||||
impl Options {
|
||||
pub fn storage(self, storage: Location) -> Self {
|
||||
Self { storage, ..self }
|
||||
Self { storage }
|
||||
}
|
||||
pub fn url(self, url: &'static [u8]) -> Self {
|
||||
Self { url, ..self }
|
||||
@@ -97,7 +100,7 @@ impl<T> iso7816::App for Authenticator<T> {
|
||||
|
||||
impl<T> Authenticator<T>
|
||||
where
|
||||
T: client::Client + client::Ed255 + client::Tdes,
|
||||
T: client::Client + AuthClient + client::Ed255 + client::Tdes,
|
||||
{
|
||||
pub fn new(trussed: T, options: Options) -> Self {
|
||||
// seems like RefCell is not the right thing, we want something like `Rc` instead,
|
||||
@@ -216,7 +219,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> {
|
||||
impl<'a, T: trussed::Client + AuthClient + trussed::client::Ed255> LoadedAuthenticator<'a, T> {
|
||||
pub fn yubico_set_administration_key<const R: usize>(
|
||||
&mut self,
|
||||
data: &[u8],
|
||||
@@ -280,24 +283,13 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T>
|
||||
// maybe reserve this for the case VerifyLogin::PivPin?
|
||||
pub fn login(&mut self, login: commands::VerifyLogin) -> Result {
|
||||
if let commands::VerifyLogin::PivPin(pin) = login {
|
||||
// the actual PIN verification
|
||||
if self.state.persistent.remaining_pin_retries() == 0 {
|
||||
return Err(Status::OperationBlocked);
|
||||
}
|
||||
|
||||
if self.state.persistent.verify_pin(&pin, self.trussed) {
|
||||
self.state
|
||||
.persistent
|
||||
.reset_consecutive_pin_mismatches(self.trussed);
|
||||
self.state.volatile.app_security_status.pin_verified = true;
|
||||
Ok(())
|
||||
} else {
|
||||
let remaining = self
|
||||
.state
|
||||
.persistent
|
||||
.increment_consecutive_pin_mismatches(self.trussed);
|
||||
// should we logout here?
|
||||
self.state.volatile.app_security_status.pin_verified = false;
|
||||
let remaining = self.state.persistent.remaining_pin_retries(self.trussed);
|
||||
Err(Status::RemainingRetries(remaining))
|
||||
}
|
||||
} else {
|
||||
@@ -322,7 +314,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T>
|
||||
if self.state.volatile.app_security_status.pin_verified {
|
||||
Ok(())
|
||||
} else {
|
||||
let retries = self.state.persistent.remaining_pin_retries();
|
||||
let retries = self.state.persistent.remaining_pin_retries(self.trussed);
|
||||
Err(Status::RemainingRetries(retries))
|
||||
}
|
||||
}
|
||||
@@ -338,45 +330,25 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T>
|
||||
}
|
||||
|
||||
pub fn change_pin(&mut self, old_pin: commands::Pin, new_pin: commands::Pin) -> Result {
|
||||
if self.state.persistent.remaining_pin_retries() == 0 {
|
||||
return Err(Status::OperationBlocked);
|
||||
}
|
||||
|
||||
if !self.state.persistent.verify_pin(&old_pin, self.trussed) {
|
||||
let remaining = self
|
||||
.state
|
||||
.persistent
|
||||
.increment_consecutive_pin_mismatches(self.trussed);
|
||||
self.state.volatile.app_security_status.pin_verified = false;
|
||||
return Err(Status::RemainingRetries(remaining));
|
||||
}
|
||||
|
||||
self.state
|
||||
if !self
|
||||
.state
|
||||
.persistent
|
||||
.reset_consecutive_pin_mismatches(self.trussed);
|
||||
self.state.persistent.set_pin(new_pin, self.trussed);
|
||||
.change_pin(&old_pin, &new_pin, self.trussed)
|
||||
{
|
||||
return Err(Status::VerificationFailed);
|
||||
}
|
||||
self.state.volatile.app_security_status.pin_verified = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn change_puk(&mut self, old_puk: commands::Puk, new_puk: commands::Puk) -> Result {
|
||||
if self.state.persistent.remaining_puk_retries() == 0 {
|
||||
return Err(Status::OperationBlocked);
|
||||
}
|
||||
|
||||
if !self.state.persistent.verify_puk(&old_puk, self.trussed) {
|
||||
let remaining = self
|
||||
.state
|
||||
.persistent
|
||||
.increment_consecutive_puk_mismatches(self.trussed);
|
||||
self.state.volatile.app_security_status.puk_verified = false;
|
||||
return Err(Status::RemainingRetries(remaining));
|
||||
}
|
||||
|
||||
self.state
|
||||
if !self
|
||||
.state
|
||||
.persistent
|
||||
.reset_consecutive_puk_mismatches(self.trussed);
|
||||
self.state.persistent.set_puk(new_puk, self.trussed);
|
||||
.change_puk(&old_puk, &new_puk, self.trussed)
|
||||
{
|
||||
return Err(Status::VerificationFailed);
|
||||
}
|
||||
self.state.volatile.app_security_status.puk_verified = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -979,7 +951,9 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T>
|
||||
{
|
||||
return Err(Status::VerificationFailed);
|
||||
}
|
||||
self.state.persistent.set_pin(Pin(data.pin), self.trussed);
|
||||
self.state
|
||||
.persistent
|
||||
.reset_pin(Pin(data.pin), self.trussed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+123
-121
@@ -14,6 +14,7 @@ use trussed::{
|
||||
syscall, try_syscall,
|
||||
types::{KeyId, KeySerialization, Location, Mechanism, PathBuf, StorageAttributes},
|
||||
};
|
||||
use trussed_auth::AuthClient;
|
||||
|
||||
use crate::piv_types::CardHolderUniqueIdentifier;
|
||||
use crate::{constants::*, piv_types::AsymmetricAlgorithms};
|
||||
@@ -188,9 +189,9 @@ pub struct State {
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn load(
|
||||
pub fn load<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
client: &mut impl trussed::Client,
|
||||
client: &mut T,
|
||||
storage: Location,
|
||||
) -> Result<LoadedState<'_>, Status> {
|
||||
if self.persistent.is_none() {
|
||||
@@ -202,9 +203,9 @@ impl State {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn persistent(
|
||||
pub fn persistent<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
client: &mut impl trussed::Client,
|
||||
client: &mut T,
|
||||
storage: Location,
|
||||
) -> Result<&mut Persistent, Status> {
|
||||
Ok(self.load(client, storage)?.persistent)
|
||||
@@ -226,18 +227,23 @@ fn volatile() -> Location {
|
||||
Location::Volatile
|
||||
}
|
||||
|
||||
enum PinType {
|
||||
Puk,
|
||||
UserPin,
|
||||
}
|
||||
|
||||
impl From<PinType> for trussed_auth::PinId {
|
||||
fn from(value: PinType) -> Self {
|
||||
match value {
|
||||
PinType::UserPin => 0.into(),
|
||||
PinType::Puk => 1.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Persistent {
|
||||
pub keys: Keys,
|
||||
consecutive_pin_mismatches: u8,
|
||||
consecutive_puk_mismatches: u8,
|
||||
// the PIN can be 6-8 digits, padded with 0xFF if <8
|
||||
// we just store all of them for now.
|
||||
pin: Pin,
|
||||
// the PUK should be 8 digits, but it seems Yubico allows 6-8
|
||||
// like for PIN
|
||||
puk: Puk,
|
||||
// pin_hash: Option<[u8; 16]>,
|
||||
// Ideally, we'd dogfood a "Monotonic Counter" from `trussed`.
|
||||
timestamp: u32,
|
||||
#[serde(skip, default = "volatile")]
|
||||
@@ -322,119 +328,114 @@ impl Persistent {
|
||||
// hmm...!
|
||||
pub const PUK_RETRIES_DEFAULT: u8 = 5;
|
||||
const FILENAME: &'static [u8] = b"persistent-state.cbor";
|
||||
const DEFAULT_PIN: &'static [u8] = b"123456\xff\xff";
|
||||
const DEFAULT_PUK: &'static [u8] = b"12345678";
|
||||
const DEFAULT_PIN: Pin = Pin(*b"123456\xff\xff");
|
||||
const DEFAULT_PUK: Puk = Puk(*b"12345678");
|
||||
|
||||
pub fn remaining_pin_retries(&self) -> u8 {
|
||||
if self.consecutive_pin_mismatches >= Self::PIN_RETRIES_DEFAULT {
|
||||
0
|
||||
} else {
|
||||
Self::PIN_RETRIES_DEFAULT - self.consecutive_pin_mismatches
|
||||
}
|
||||
pub fn remaining_pin_retries<T: trussed::Client + AuthClient>(&self, client: &mut T) -> u8 {
|
||||
try_syscall!(client.pin_retries(PinType::UserPin))
|
||||
.map(|r| r.retries.unwrap_or_default())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn remaining_puk_retries(&self) -> u8 {
|
||||
if self.consecutive_puk_mismatches >= Self::PUK_RETRIES_DEFAULT {
|
||||
0
|
||||
} else {
|
||||
Self::PUK_RETRIES_DEFAULT - self.consecutive_puk_mismatches
|
||||
}
|
||||
pub fn remaining_puk_retries<T: trussed::Client + AuthClient>(&self, client: &mut T) -> u8 {
|
||||
try_syscall!(client.pin_retries(PinType::Puk))
|
||||
.map(|r| r.retries.unwrap_or_default())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
// FIXME: revisit with trussed pin management
|
||||
pub fn verify_pin(&mut self, other_pin: &Pin, client: &mut impl trussed::Client) -> bool {
|
||||
if self.remaining_pin_retries() == 0 {
|
||||
return false;
|
||||
}
|
||||
self.consecutive_pin_mismatches += 1;
|
||||
self.save(client);
|
||||
if self.pin == *other_pin {
|
||||
self.consecutive_pin_mismatches = 0;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: revisit with trussed pin management
|
||||
pub fn verify_puk(&mut self, other_puk: &Puk, client: &mut impl trussed::Client) -> bool {
|
||||
if self.remaining_puk_retries() == 0 {
|
||||
return false;
|
||||
}
|
||||
self.consecutive_puk_mismatches += 1;
|
||||
self.save(client);
|
||||
if self.puk == *other_puk {
|
||||
self.consecutive_puk_mismatches = 0;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_pin(&mut self, new_pin: Pin, client: &mut impl trussed::Client) {
|
||||
self.pin = new_pin;
|
||||
self.consecutive_pin_mismatches = 0;
|
||||
self.save(client);
|
||||
}
|
||||
|
||||
pub fn set_puk(&mut self, new_puk: Puk, client: &mut impl trussed::Client) {
|
||||
self.puk = new_puk;
|
||||
self.consecutive_puk_mismatches = 0;
|
||||
self.save(client);
|
||||
}
|
||||
|
||||
pub fn reset_pin(&mut self, client: &mut impl trussed::Client) {
|
||||
self.set_pin(Pin::try_from(Self::DEFAULT_PIN).unwrap(), client);
|
||||
self.reset_consecutive_pin_mismatches(client);
|
||||
}
|
||||
|
||||
pub fn reset_puk(&mut self, client: &mut impl trussed::Client) {
|
||||
self.set_puk(Puk::try_from(Self::DEFAULT_PUK).unwrap(), client);
|
||||
self.reset_consecutive_puk_mismatches(client);
|
||||
}
|
||||
|
||||
pub fn increment_consecutive_pin_mismatches(
|
||||
pub fn verify_pin<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
client: &mut impl trussed::Client,
|
||||
) -> u8 {
|
||||
if self.consecutive_pin_mismatches >= Self::PIN_RETRIES_DEFAULT {
|
||||
return 0;
|
||||
}
|
||||
|
||||
self.consecutive_pin_mismatches += 1;
|
||||
self.save(client);
|
||||
Self::PIN_RETRIES_DEFAULT - self.consecutive_pin_mismatches
|
||||
value: &Pin,
|
||||
client: &mut T,
|
||||
) -> bool {
|
||||
let pin = Bytes::from_slice(&value.0).expect("Convertion of static array");
|
||||
try_syscall!(client.check_pin(PinType::UserPin, pin))
|
||||
.map(|r| r.success)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn increment_consecutive_puk_mismatches(
|
||||
pub fn verify_puk<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
client: &mut impl trussed::Client,
|
||||
) -> u8 {
|
||||
if self.consecutive_puk_mismatches >= Self::PUK_RETRIES_DEFAULT {
|
||||
return 0;
|
||||
}
|
||||
|
||||
self.consecutive_puk_mismatches += 1;
|
||||
self.save(client);
|
||||
Self::PUK_RETRIES_DEFAULT - self.consecutive_puk_mismatches
|
||||
value: &Puk,
|
||||
client: &mut T,
|
||||
) -> bool {
|
||||
let puk = Bytes::from_slice(&value.0).expect("Convertion of static array");
|
||||
try_syscall!(client.check_pin(PinType::Puk, puk))
|
||||
.map(|r| r.success)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn reset_consecutive_pin_mismatches(&mut self, client: &mut impl trussed::Client) -> u8 {
|
||||
if self.consecutive_pin_mismatches != 0 {
|
||||
self.consecutive_pin_mismatches = 0;
|
||||
self.save(client);
|
||||
}
|
||||
|
||||
Self::PIN_RETRIES_DEFAULT
|
||||
pub fn change_pin<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
old_value: &Pin,
|
||||
new_value: &Pin,
|
||||
client: &mut T,
|
||||
) -> bool {
|
||||
let old_pin = Bytes::from_slice(&old_value.0).expect("Convertion of static array");
|
||||
let new_pin = Bytes::from_slice(&new_value.0).expect("Convertion of static array");
|
||||
try_syscall!(client.change_pin(PinType::UserPin, old_pin, new_pin))
|
||||
.map(|r| r.success)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn reset_consecutive_puk_mismatches(&mut self, client: &mut impl trussed::Client) -> u8 {
|
||||
if self.consecutive_puk_mismatches != 0 {
|
||||
self.consecutive_puk_mismatches = 0;
|
||||
self.save(client);
|
||||
}
|
||||
pub fn change_puk<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
old_value: &Puk,
|
||||
new_value: &Puk,
|
||||
client: &mut T,
|
||||
) -> bool {
|
||||
let old_puk = Bytes::from_slice(&old_value.0).expect("Convertion of static array");
|
||||
let new_puk = Bytes::from_slice(&new_value.0).expect("Convertion of static array");
|
||||
try_syscall!(client.change_pin(PinType::UserPin, old_puk, new_puk))
|
||||
.map(|r| r.success)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
Self::PUK_RETRIES_DEFAULT
|
||||
pub fn set_pin<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
new_pin: Pin,
|
||||
client: &mut T,
|
||||
) -> Result<(), Status> {
|
||||
let new_pin = Bytes::from_slice(&new_pin.0).expect("Convertion of static array");
|
||||
try_syscall!(client.set_pin(
|
||||
PinType::UserPin,
|
||||
new_pin,
|
||||
Some(Self::PIN_RETRIES_DEFAULT),
|
||||
true
|
||||
))
|
||||
.map_err(|_err| {
|
||||
error!("Failed to set pin");
|
||||
Status::UnspecifiedPersistentExecutionError
|
||||
})
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
pub fn set_puk<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
new_puk: Puk,
|
||||
client: &mut T,
|
||||
) -> Result<(), Status> {
|
||||
let new_puk = Bytes::from_slice(&new_puk.0).expect("Convertion of static array");
|
||||
try_syscall!(client.set_pin(PinType::Puk, new_puk, Some(Self::PUK_RETRIES_DEFAULT), true))
|
||||
.map_err(|_err| {
|
||||
error!("Failed to set puk");
|
||||
Status::UnspecifiedPersistentExecutionError
|
||||
})
|
||||
.map(drop)
|
||||
}
|
||||
pub fn reset_pin<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
new_pin: Pin,
|
||||
client: &mut T,
|
||||
) -> Result<(), Status> {
|
||||
self.set_pin(new_pin, client)
|
||||
}
|
||||
pub fn reset_puk<T: trussed::Client + AuthClient>(
|
||||
&mut self,
|
||||
new_puk: Puk,
|
||||
client: &mut T,
|
||||
) -> Result<(), Status> {
|
||||
self.set_puk(new_puk, client)
|
||||
}
|
||||
|
||||
pub fn reset_administration_key(&mut self, client: &mut impl trussed::Client) {
|
||||
@@ -494,7 +495,10 @@ impl Persistent {
|
||||
id
|
||||
}
|
||||
|
||||
pub fn initialize(client: &mut impl trussed::Client, storage: Location) -> Self {
|
||||
pub fn initialize<T: trussed::Client + AuthClient>(
|
||||
client: &mut T,
|
||||
storage: Location,
|
||||
) -> Result<Self, Status> {
|
||||
info!("initializing PIV state");
|
||||
let administration = KeyWithAlg {
|
||||
id: syscall!(client.unsafe_inject_key(
|
||||
@@ -549,26 +553,24 @@ impl Persistent {
|
||||
|
||||
let mut state = Self {
|
||||
keys,
|
||||
consecutive_pin_mismatches: 0,
|
||||
consecutive_puk_mismatches: 0,
|
||||
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
|
||||
state.reset_pin(Self::DEFAULT_PIN, client)?;
|
||||
state.reset_puk(Self::DEFAULT_PUK, client)?;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub fn load_or_initialize(
|
||||
client: &mut impl trussed::Client,
|
||||
pub fn load_or_initialize<T: trussed::Client + AuthClient>(
|
||||
client: &mut T,
|
||||
storage: Location,
|
||||
) -> Result<Self, Status> {
|
||||
// todo: can't seem to combine load + initialize without code repetition
|
||||
let data = load_if_exists(client, storage, &PathBuf::from(Self::FILENAME))?;
|
||||
let Some(bytes) = data else {
|
||||
return Ok( Self::initialize(client, storage));
|
||||
return Self::initialize(client, storage);
|
||||
};
|
||||
|
||||
let mut parsed: Self = trussed::cbor_deserialize(&bytes).map_err(|_err| {
|
||||
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
// Copyright (C) 2022 Nitrokey GmbH
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
//! Virtual trussed client (mostly for testing)
|
||||
|
||||
pub 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_rsa_alloc::SoftwareRsa;
|
||||
|
||||
/// Backends used by opcard
|
||||
pub const BACKENDS: &[BackendId<Backend>] = &[
|
||||
BackendId::Custom(Backend::Auth),
|
||||
BackendId::Custom(Backend::Rsa),
|
||||
BackendId::Core,
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Backend {
|
||||
Auth,
|
||||
Rsa,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Extension {
|
||||
Auth,
|
||||
}
|
||||
|
||||
impl From<Extension> for u8 {
|
||||
fn from(extension: Extension) -> Self {
|
||||
match extension {
|
||||
Extension::Auth => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Extension {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(id: u8) -> Result<Self, Self::Error> {
|
||||
match id {
|
||||
0 => Ok(Extension::Auth),
|
||||
_ => Err(Error::InternalError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch implementation with the backends required by opcard
|
||||
#[derive(Debug)]
|
||||
pub struct Dispatch {
|
||||
auth: AuthBackend,
|
||||
}
|
||||
|
||||
/// Dispatch context for the backends required by opcard
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DispatchContext {
|
||||
auth: AuthContext,
|
||||
}
|
||||
|
||||
impl Dispatch {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
auth: AuthBackend::new(Location::Internal),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_hw_key(hw_key: Bytes<MAX_HW_KEY_LEN>) -> Self {
|
||||
Self {
|
||||
auth: AuthBackend::with_hw_key(Location::Internal, hw_key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Dispatch {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionDispatch for Dispatch {
|
||||
type BackendId = Backend;
|
||||
type Context = DispatchContext;
|
||||
type ExtensionId = Extension;
|
||||
|
||||
fn core_request<P: Platform>(
|
||||
&mut self,
|
||||
backend: &Self::BackendId,
|
||||
ctx: &mut Context<Self::Context>,
|
||||
request: &Request,
|
||||
resources: &mut ServiceResources<P>,
|
||||
) -> Result<Reply, Error> {
|
||||
match backend {
|
||||
Backend::Auth => {
|
||||
self.auth
|
||||
.request(&mut ctx.core, &mut ctx.backends.auth, request, resources)
|
||||
}
|
||||
Backend::Rsa => SoftwareRsa.request(&mut ctx.core, &mut (), request, resources),
|
||||
}
|
||||
}
|
||||
|
||||
fn extension_request<P: Platform>(
|
||||
&mut self,
|
||||
backend: &Self::BackendId,
|
||||
extension: &Self::ExtensionId,
|
||||
ctx: &mut Context<Self::Context>,
|
||||
request: &request::SerdeExtension,
|
||||
resources: &mut ServiceResources<P>,
|
||||
) -> Result<reply::SerdeExtension, Error> {
|
||||
match backend {
|
||||
Backend::Auth => match extension {
|
||||
Extension::Auth => self.auth.extension_request_serialized(
|
||||
&mut ctx.core,
|
||||
&mut ctx.backends.auth,
|
||||
request,
|
||||
resources,
|
||||
),
|
||||
},
|
||||
Backend::Rsa => Err(Error::RequestNotAvailable),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionId<AuthExtension> for Dispatch {
|
||||
type Id = Extension;
|
||||
|
||||
const ID: Self::Id = Self::Id::Auth;
|
||||
}
|
||||
}
|
||||
|
||||
use std::path::PathBuf;
|
||||
use trussed::{
|
||||
types::Bytes,
|
||||
virt::{self, Client, Filesystem, Ram, StoreProvider},
|
||||
};
|
||||
|
||||
/// Client type using a dispatcher with the backends required by opcard
|
||||
pub type VirtClient<S> = Client<S, dispatch::Dispatch>;
|
||||
|
||||
/// Run a client using a provided store
|
||||
pub fn with_client<S, R, F>(store: S, client_id: &str, f: F) -> R
|
||||
where
|
||||
F: FnOnce(VirtClient<S>) -> R,
|
||||
S: StoreProvider,
|
||||
{
|
||||
#[allow(clippy::unwrap_used)]
|
||||
virt::with_platform(store, |platform| {
|
||||
platform.run_client_with_backends(
|
||||
client_id,
|
||||
dispatch::Dispatch::with_hw_key(Bytes::from_slice(b"some bytes").unwrap()),
|
||||
dispatch::BACKENDS,
|
||||
f,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the backend with the extensions required by opcard
|
||||
/// using storage backed by a file
|
||||
pub fn with_fs_client<P, R, F>(internal: P, client_id: &str, f: F) -> R
|
||||
where
|
||||
F: FnOnce(VirtClient<Filesystem>) -> R,
|
||||
P: Into<PathBuf>,
|
||||
{
|
||||
with_client(Filesystem::new(internal), client_id, f)
|
||||
}
|
||||
|
||||
/// Run the backend with the extensions required by opcard
|
||||
/// using a RAM file storage
|
||||
pub fn with_ram_client<R, F>(client_id: &str, f: F) -> R
|
||||
where
|
||||
F: FnOnce(VirtClient<Ram>) -> R,
|
||||
{
|
||||
with_client(Ram::default(), client_id, f)
|
||||
}
|
||||
+4
-3
@@ -3,7 +3,8 @@
|
||||
|
||||
use iso7816::{command::FromSliceError, Command, Status};
|
||||
use trussed::virt::Ram;
|
||||
use trussed_rsa_alloc::virt::Client;
|
||||
|
||||
use crate::virt::VirtClient;
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
@@ -19,12 +20,12 @@ const RESPONSE_LEN: usize = 7609;
|
||||
pub struct VpiccCard {
|
||||
request_buffer: RequestBuffer<REQUEST_LEN>,
|
||||
response_buffer: ResponseBuffer<RESPONSE_LEN>,
|
||||
card: Authenticator<Client<Ram>>,
|
||||
card: Authenticator<VirtClient<Ram>>,
|
||||
}
|
||||
|
||||
impl VpiccCard {
|
||||
/// Creates a new virtual smart card from the given card.
|
||||
pub fn new(card: Authenticator<Client<Ram>>) -> Self {
|
||||
pub fn new(card: Authenticator<VirtClient<Ram>>) -> Self {
|
||||
Self {
|
||||
request_buffer: Default::default(),
|
||||
response_buffer: Default::default(),
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2022 Nitrokey GmbH
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
use piv_authenticator::{vpicc::VpiccCard, Authenticator, Options};
|
||||
use piv_authenticator::{virt::with_ram_client, vpicc::VpiccCard, Authenticator, Options};
|
||||
|
||||
use std::{sync::mpsc, thread::sleep, time::Duration};
|
||||
use stoppable_thread::spawn;
|
||||
@@ -17,7 +17,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| {
|
||||
with_ram_client("opcard", |client| {
|
||||
let card = Authenticator::new(client, Options::default());
|
||||
let mut vpicc_card = VpiccCard::new(card);
|
||||
let mut result = Ok(());
|
||||
|
||||
+6
-4
@@ -11,14 +11,16 @@ macro_rules! cmd {
|
||||
};
|
||||
}
|
||||
|
||||
use piv_authenticator::{Authenticator, Options};
|
||||
use piv_authenticator::{
|
||||
virt::{with_ram_client, VirtClient},
|
||||
Authenticator, Options,
|
||||
};
|
||||
use trussed::virt::Ram;
|
||||
use trussed_rsa_alloc::virt::Client;
|
||||
|
||||
pub type Piv = piv_authenticator::Authenticator<Client<Ram>>;
|
||||
pub type Piv = piv_authenticator::Authenticator<VirtClient<Ram>>;
|
||||
|
||||
pub fn piv<R>(test: impl FnOnce(&mut Piv) -> R) -> R {
|
||||
trussed_rsa_alloc::virt::with_ram_client("test", |client| {
|
||||
with_ram_client("test", |client| {
|
||||
let mut piv_app = Authenticator::new(client, Options::default());
|
||||
test(&mut piv_app)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user