mirror of
https://github.com/trussed-dev/fido-authenticator.git
synced 2026-06-20 04:16:16 -07:00
Cargo fmt round
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
use trussed::types::{CertId, KeyId};
|
||||
|
||||
pub const FIDO2_UP_TIMEOUT: u32 = 30_000;
|
||||
pub const U2F_UP_TIMEOUT: u32 = 250;
|
||||
pub const U2F_UP_TIMEOUT: u32 = 250;
|
||||
|
||||
pub const ATTESTATION_CERT_ID: CertId = CertId::from_special(0);
|
||||
pub const ATTESTATION_KEY_ID: KeyId = KeyId::from_special(0);
|
||||
|
||||
+55
-56
@@ -2,26 +2,18 @@
|
||||
|
||||
use core::cmp::Ordering;
|
||||
|
||||
use trussed::{
|
||||
client, syscall, try_syscall,
|
||||
types::KeyId,
|
||||
};
|
||||
use trussed::{client, syscall, try_syscall, types::KeyId};
|
||||
|
||||
pub(crate) use ctap_types::{
|
||||
Bytes, String,
|
||||
// authenticator::{ctap1, ctap2, Error, Request, Response},
|
||||
ctap2::credential_management::CredentialProtectionPolicy,
|
||||
sizes::*,
|
||||
webauthn::PublicKeyCredentialDescriptor,
|
||||
Bytes,
|
||||
String,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Authenticator,
|
||||
Error,
|
||||
Result,
|
||||
UserPresence,
|
||||
};
|
||||
|
||||
use crate::{Authenticator, Error, Result, UserPresence};
|
||||
|
||||
/// As signaled in `get_info`.
|
||||
///
|
||||
@@ -49,7 +41,9 @@ impl TryFrom<EncryptedSerializedCredential> for CredentialId {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(esc: EncryptedSerializedCredential) -> Result<CredentialId> {
|
||||
Ok(CredentialId(trussed::cbor_serialize_bytes(&esc.0).map_err(|_| Error::Other)?))
|
||||
Ok(CredentialId(
|
||||
trussed::cbor_serialize_bytes(&esc.0).map_err(|_| Error::Other)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +54,7 @@ impl TryFrom<CredentialId> for EncryptedSerializedCredential {
|
||||
|
||||
fn try_from(cid: CredentialId) -> Result<EncryptedSerializedCredential> {
|
||||
let encrypted_serialized_credential = EncryptedSerializedCredential(
|
||||
ctap_types::serde::cbor_deserialize(&cid.0).map_err(|_| Error::InvalidCredential)?
|
||||
ctap_types::serde::cbor_deserialize(&cid.0).map_err(|_| Error::InvalidCredential)?,
|
||||
);
|
||||
Ok(encrypted_serialized_credential)
|
||||
}
|
||||
@@ -78,7 +72,9 @@ pub enum Key {
|
||||
}
|
||||
|
||||
/// The main content of a `Credential`.
|
||||
#[derive(Clone, Debug, PartialEq, serde_indexed::DeserializeIndexed, serde_indexed::SerializeIndexed)]
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, serde_indexed::DeserializeIndexed, serde_indexed::SerializeIndexed,
|
||||
)]
|
||||
pub struct CredentialData {
|
||||
// id, name, url
|
||||
pub rp: ctap_types::webauthn::PublicKeyCredentialRpEntity,
|
||||
@@ -103,7 +99,6 @@ pub struct CredentialData {
|
||||
pub hmac_secret: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cred_protect: Option<CredentialProtectionPolicy>,
|
||||
|
||||
// TODO: add `sig_counter: Option<CounterId>`,
|
||||
// and grant RKs a per-credential sig-counter.
|
||||
}
|
||||
@@ -139,9 +134,7 @@ impl core::ops::Deref for Credential {
|
||||
/// Likely comparison based on timestamp would be good enough?
|
||||
impl PartialEq for Credential {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
(self.creation_time == other.creation_time)
|
||||
&&
|
||||
(self.key == other.key)
|
||||
(self.creation_time == other.creation_time) && (self.key == other.key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +176,7 @@ impl From<CredentialId> for PublicKeyCredentialDescriptor {
|
||||
let mut key_type = String::new();
|
||||
key_type.push_str("public-key").unwrap();
|
||||
key_type
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,9 +194,7 @@ impl Credential {
|
||||
hmac_secret: Option<bool>,
|
||||
cred_protect: Option<CredentialProtectionPolicy>,
|
||||
nonce: [u8; 12],
|
||||
)
|
||||
-> Self
|
||||
{
|
||||
) -> Self {
|
||||
info!("credential for algorithm {}", algorithm);
|
||||
let data = CredentialData {
|
||||
rp: rp.clone(),
|
||||
@@ -243,9 +234,7 @@ impl Credential {
|
||||
trussed: &mut T,
|
||||
key_encryption_key: KeyId,
|
||||
rp_id_hash: Option<&Bytes<32>>,
|
||||
)
|
||||
-> Result<CredentialId>
|
||||
{
|
||||
) -> Result<CredentialId> {
|
||||
let serialized_credential = self.strip().serialize()?;
|
||||
let message = &serialized_credential;
|
||||
// info!("serialized cred = {:?}", message).ok();
|
||||
@@ -255,14 +244,14 @@ impl Credential {
|
||||
} else {
|
||||
syscall!(trussed.hash_sha256(self.rp.id.as_ref()))
|
||||
.hash
|
||||
.to_bytes().map_err(|_| Error::Other)?
|
||||
.to_bytes()
|
||||
.map_err(|_| Error::Other)?
|
||||
};
|
||||
|
||||
let associated_data = &rp_id_hash[..];
|
||||
let nonce: [u8; 12] = self.nonce.as_slice().try_into().unwrap();
|
||||
let encrypted_serialized_credential = EncryptedSerializedCredential(
|
||||
syscall!(trussed.encrypt_chacha8poly1305(
|
||||
key_encryption_key, message, associated_data, Some(&nonce))));
|
||||
let encrypted_serialized_credential = EncryptedSerializedCredential(syscall!(trussed
|
||||
.encrypt_chacha8poly1305(key_encryption_key, message, associated_data, Some(&nonce))));
|
||||
let credential_id: CredentialId = encrypted_serialized_credential.try_into().unwrap();
|
||||
|
||||
Ok(credential_id)
|
||||
@@ -283,12 +272,10 @@ impl Credential {
|
||||
}
|
||||
|
||||
pub fn try_from<UP: UserPresence, T: client::Client + client::Chacha8Poly1305>(
|
||||
authnr: &mut Authenticator<UP,T>,
|
||||
authnr: &mut Authenticator<UP, T>,
|
||||
rp_id_hash: &Bytes<32>,
|
||||
descriptor: &PublicKeyCredentialDescriptor,
|
||||
)
|
||||
-> Result<Self>
|
||||
{
|
||||
) -> Result<Self> {
|
||||
Self::try_from_bytes(authnr, rp_id_hash, &descriptor.id)
|
||||
}
|
||||
|
||||
@@ -296,18 +283,17 @@ impl Credential {
|
||||
authnr: &mut Authenticator<UP, T>,
|
||||
rp_id_hash: &Bytes<32>,
|
||||
id: &[u8],
|
||||
)
|
||||
-> Result<Self>
|
||||
{
|
||||
|
||||
) -> Result<Self> {
|
||||
let mut cred: Bytes<MAX_CREDENTIAL_ID_LENGTH> = Bytes::new();
|
||||
cred.extend_from_slice(id).map_err(|_| Error::InvalidCredential)?;
|
||||
cred.extend_from_slice(id)
|
||||
.map_err(|_| Error::InvalidCredential)?;
|
||||
|
||||
let encrypted_serialized = EncryptedSerializedCredential::try_from(
|
||||
CredentialId(cred)
|
||||
)?;
|
||||
let encrypted_serialized = EncryptedSerializedCredential::try_from(CredentialId(cred))?;
|
||||
|
||||
let kek = authnr.state.persistent.key_encryption_key(&mut authnr.trussed)?;
|
||||
let kek = authnr
|
||||
.state
|
||||
.persistent
|
||||
.key_encryption_key(&mut authnr.trussed)?;
|
||||
|
||||
let serialized = try_syscall!(authnr.trussed.decrypt_chacha8poly1305(
|
||||
// TODO: use RpId as associated data here?
|
||||
@@ -317,11 +303,12 @@ impl Credential {
|
||||
&encrypted_serialized.0.nonce,
|
||||
&encrypted_serialized.0.tag,
|
||||
))
|
||||
.map_err(|_| Error::InvalidCredential)?.plaintext
|
||||
.ok_or(Error::InvalidCredential)?;
|
||||
.map_err(|_| Error::InvalidCredential)?
|
||||
.plaintext
|
||||
.ok_or(Error::InvalidCredential)?;
|
||||
|
||||
let credential = Credential::deserialize(&serialized)
|
||||
.map_err(|_| Error::InvalidCredential)?;
|
||||
let credential =
|
||||
Credential::deserialize(&serialized).map_err(|_| Error::InvalidCredential)?;
|
||||
|
||||
Ok(credential)
|
||||
}
|
||||
@@ -362,7 +349,7 @@ mod test {
|
||||
url: None,
|
||||
},
|
||||
user: PublicKeyCredentialUserEntity {
|
||||
id: Bytes::from_slice(&[1,2,3]).unwrap(),
|
||||
id: Bytes::from_slice(&[1, 2, 3]).unwrap(),
|
||||
icon: None,
|
||||
name: None,
|
||||
display_name: None,
|
||||
@@ -370,7 +357,7 @@ mod test {
|
||||
creation_time: 123,
|
||||
use_counter: false,
|
||||
algorithm: -7,
|
||||
key: Key::WrappedKey(Bytes::from_slice(&[1,2,3]).unwrap()),
|
||||
key: Key::WrappedKey(Bytes::from_slice(&[1, 2, 3]).unwrap()),
|
||||
hmac_secret: Some(false),
|
||||
cred_protect: None,
|
||||
};
|
||||
@@ -378,7 +365,11 @@ mod test {
|
||||
}
|
||||
|
||||
fn random_bytes<const N: usize>() -> Bytes<N> {
|
||||
use rand::{RngCore, distributions::{Distribution, Uniform}, rngs::OsRng};
|
||||
use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
rngs::OsRng,
|
||||
RngCore,
|
||||
};
|
||||
let mut bytes = Bytes::default();
|
||||
|
||||
let between = Uniform::from(0..(N + 1));
|
||||
@@ -392,7 +383,7 @@ mod test {
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn maybe_random_bytes<const N: usize>() -> Option<Bytes<N>> {
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
if OsRng.next_u32() & 1 != 0 {
|
||||
Some(random_bytes())
|
||||
} else {
|
||||
@@ -401,18 +392,26 @@ mod test {
|
||||
}
|
||||
|
||||
fn random_string<const N: usize>() -> String<N> {
|
||||
use rand::{
|
||||
distributions::{Alphanumeric, Distribution, Uniform},
|
||||
rngs::OsRng,
|
||||
Rng,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use rand::{Rng, distributions::{Alphanumeric, Distribution, Uniform}, rngs::OsRng};
|
||||
|
||||
let between = Uniform::from(0..(N + 1));
|
||||
let n = between.sample(&mut OsRng);
|
||||
|
||||
let std_string: std::string::String = OsRng.sample_iter(&Alphanumeric).take(n).map(char::from).collect();
|
||||
let std_string: std::string::String = OsRng
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(n)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
String::from_str(&std_string).unwrap()
|
||||
}
|
||||
|
||||
fn maybe_random_string<const N: usize>() -> Option<String<N>> {
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
if OsRng.next_u32() & 1 != 0 {
|
||||
Some(random_string())
|
||||
} else {
|
||||
@@ -430,7 +429,7 @@ mod test {
|
||||
url: maybe_random_string(),
|
||||
},
|
||||
user: PublicKeyCredentialUserEntity {
|
||||
id: random_bytes(),//Bytes::from_slice(&[1,2,3]).unwrap(),
|
||||
id: random_bytes(), //Bytes::from_slice(&[1,2,3]).unwrap(),
|
||||
icon: maybe_random_string(),
|
||||
name: maybe_random_string(),
|
||||
display_name: maybe_random_string(),
|
||||
@@ -518,5 +517,5 @@ mod test {
|
||||
|
||||
// TestResult::from_bool(credential_data == deserialized)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
+84
-65
@@ -1,39 +1,22 @@
|
||||
//! The `ctap_types::ctap1::Authenticator` implementation.
|
||||
|
||||
use ctap_types::{
|
||||
ctap1::{
|
||||
Authenticator,
|
||||
ControlByte,
|
||||
register, authenticate,
|
||||
Result,
|
||||
Error,
|
||||
},
|
||||
ctap1::{authenticate, register, Authenticator, ControlByte, Error, Result},
|
||||
heapless_bytes::Bytes,
|
||||
};
|
||||
|
||||
use trussed::{
|
||||
syscall,
|
||||
types::{
|
||||
KeySerialization,
|
||||
Mechanism,
|
||||
SignatureSerialization,
|
||||
Location,
|
||||
},
|
||||
types::{KeySerialization, Location, Mechanism, SignatureSerialization},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
credential::{
|
||||
self,
|
||||
Credential,
|
||||
Key,
|
||||
},
|
||||
constants,
|
||||
SigningAlgorithm,
|
||||
TrussedRequirements,
|
||||
UserPresence,
|
||||
credential::{self, Credential, Key},
|
||||
SigningAlgorithm, TrussedRequirements, UserPresence,
|
||||
};
|
||||
|
||||
type Commitment = Bytes::<324>;
|
||||
type Commitment = Bytes<324>;
|
||||
|
||||
/// Implement `ctap1::Authenticator` for our Authenticator.
|
||||
///
|
||||
@@ -41,8 +24,7 @@ type Commitment = Bytes::<324>;
|
||||
/// The "proposed standard" of U2F V1.2 applies to CTAP1.
|
||||
/// - [Message formats](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html)
|
||||
/// - [App ID](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-appid-and-facets-v1.2-ps-20170411.html)
|
||||
impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenticator<UP, T>
|
||||
{
|
||||
impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenticator<UP, T> {
|
||||
/// Register a new credential, this always uses P-256 keys.
|
||||
///
|
||||
/// Note that attestation is mandatory in CTAP1/U2F, so if the state
|
||||
@@ -52,35 +34,51 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
/// Also note that CTAP1 credentials should be assertable over CTAP2. I believe this is
|
||||
/// currently not the case.
|
||||
fn register(&mut self, reg: ®ister::Request) -> Result<register::Response> {
|
||||
self.up.user_present(&mut self.trussed, constants::U2F_UP_TIMEOUT)
|
||||
self.up
|
||||
.user_present(&mut self.trussed, constants::U2F_UP_TIMEOUT)
|
||||
.map_err(|_| Error::ConditionsOfUseNotSatisfied)?;
|
||||
|
||||
// Generate a new P256 key pair.
|
||||
let private_key = syscall!(self.trussed.generate_p256_private_key(Location::Volatile)).key;
|
||||
let public_key = syscall!(self.trussed.derive_p256_public_key(private_key, Location::Volatile)).key;
|
||||
let public_key = syscall!(self
|
||||
.trussed
|
||||
.derive_p256_public_key(private_key, Location::Volatile))
|
||||
.key;
|
||||
|
||||
let serialized_cose_public_key = syscall!(self.trussed.serialize_p256_key(
|
||||
public_key, KeySerialization::EcdhEsHkdf256
|
||||
)).serialized_key;
|
||||
let serialized_cose_public_key = syscall!(self
|
||||
.trussed
|
||||
.serialize_p256_key(public_key, KeySerialization::EcdhEsHkdf256))
|
||||
.serialized_key;
|
||||
syscall!(self.trussed.delete(public_key));
|
||||
let cose_key: ctap_types::cose::EcdhEsHkdf256PublicKey
|
||||
= trussed::cbor_deserialize(&serialized_cose_public_key).unwrap();
|
||||
let cose_key: ctap_types::cose::EcdhEsHkdf256PublicKey =
|
||||
trussed::cbor_deserialize(&serialized_cose_public_key).unwrap();
|
||||
|
||||
let wrapping_key = self.state.persistent.key_wrapping_key(&mut self.trussed)
|
||||
let wrapping_key = self
|
||||
.state
|
||||
.persistent
|
||||
.key_wrapping_key(&mut self.trussed)
|
||||
.map_err(|_| Error::UnspecifiedCheckingError)?;
|
||||
// debug!("wrapping u2f private key");
|
||||
|
||||
let wrapped_key = syscall!(self.trussed.wrap_key_chacha8poly1305(
|
||||
wrapping_key,
|
||||
private_key,
|
||||
®.app_id,
|
||||
)).wrapped_key;
|
||||
let wrapped_key =
|
||||
syscall!(self
|
||||
.trussed
|
||||
.wrap_key_chacha8poly1305(wrapping_key, private_key, ®.app_id,))
|
||||
.wrapped_key;
|
||||
// debug!("wrapped_key = {:?}", &wrapped_key);
|
||||
|
||||
syscall!(self.trussed.delete(private_key));
|
||||
|
||||
let key = Key::WrappedKey(wrapped_key.to_bytes().map_err(|_| Error::UnspecifiedCheckingError)?);
|
||||
let nonce = syscall!(self.trussed.random_bytes(12)).bytes.as_slice().try_into().unwrap();
|
||||
let key = Key::WrappedKey(
|
||||
wrapped_key
|
||||
.to_bytes()
|
||||
.map_err(|_| Error::UnspecifiedCheckingError)?,
|
||||
);
|
||||
let nonce = syscall!(self.trussed.random_bytes(12))
|
||||
.bytes
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let mut rp_id = heapless::String::new();
|
||||
|
||||
@@ -88,7 +86,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
// TODO: Is this true?
|
||||
// <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#cross-version-credentials>
|
||||
rp_id.push_str("u2f").ok();
|
||||
let rp = ctap_types::webauthn::PublicKeyCredentialRpEntity{
|
||||
let rp = ctap_types::webauthn::PublicKeyCredentialRpEntity {
|
||||
id: rp_id,
|
||||
name: None,
|
||||
url: None,
|
||||
@@ -105,10 +103,12 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
credential::CtapVersion::U2fV2,
|
||||
&rp,
|
||||
&user,
|
||||
|
||||
SigningAlgorithm::P256 as i32,
|
||||
key,
|
||||
self.state.persistent.timestamp(&mut self.trussed).map_err(|_| Error::NotEnoughMemory)?,
|
||||
self.state
|
||||
.persistent
|
||||
.timestamp(&mut self.trussed)
|
||||
.map_err(|_| Error::NotEnoughMemory)?,
|
||||
None,
|
||||
None,
|
||||
nonce,
|
||||
@@ -117,18 +117,24 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
// info!("made credential {:?}", &credential);
|
||||
|
||||
// 12.b generate credential ID { = AEAD(Serialize(Credential)) }
|
||||
let kek = self.state.persistent.key_encryption_key(&mut self.trussed).map_err(|_| Error::NotEnoughMemory)?;
|
||||
let credential_id = credential.id(&mut self.trussed, kek, Some(®.app_id)).map_err(|_| Error::NotEnoughMemory)?;
|
||||
let kek = self
|
||||
.state
|
||||
.persistent
|
||||
.key_encryption_key(&mut self.trussed)
|
||||
.map_err(|_| Error::NotEnoughMemory)?;
|
||||
let credential_id = credential
|
||||
.id(&mut self.trussed, kek, Some(®.app_id))
|
||||
.map_err(|_| Error::NotEnoughMemory)?;
|
||||
|
||||
let mut commitment = Commitment::new();
|
||||
|
||||
commitment.push(0).unwrap(); // reserve byte
|
||||
commitment.push(0).unwrap(); // reserve byte
|
||||
commitment.extend_from_slice(®.app_id).unwrap();
|
||||
commitment.extend_from_slice(®.challenge).unwrap();
|
||||
|
||||
commitment.extend_from_slice(&credential_id.0).unwrap();
|
||||
|
||||
commitment.push(0x04).unwrap(); // public key uncompressed byte
|
||||
commitment.push(0x04).unwrap(); // public key uncompressed byte
|
||||
commitment.extend_from_slice(&cose_key.x).unwrap();
|
||||
commitment.extend_from_slice(&cose_key.y).unwrap();
|
||||
|
||||
@@ -138,22 +144,24 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
(Some((key, cert)), _aaguid) => {
|
||||
info!("aaguid: {}", hex_str!(&_aaguid));
|
||||
(
|
||||
syscall!(
|
||||
self.trussed.sign(Mechanism::P256,
|
||||
syscall!(self.trussed.sign(
|
||||
Mechanism::P256,
|
||||
key,
|
||||
&commitment,
|
||||
SignatureSerialization::Asn1Der
|
||||
)).signature.to_bytes().unwrap(),
|
||||
cert
|
||||
))
|
||||
.signature
|
||||
.to_bytes()
|
||||
.unwrap(),
|
||||
cert,
|
||||
)
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
info!("Not provisioned with attestation key!");
|
||||
return Err(Error::KeyReferenceNotFound);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Ok(register::Response::new(
|
||||
0x05,
|
||||
&cose_key,
|
||||
@@ -176,12 +184,13 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
} else {
|
||||
Err(Error::IncorrectDataParameter)
|
||||
};
|
||||
},
|
||||
}
|
||||
ControlByte::EnforceUserPresenceAndSign => {
|
||||
self.up.user_present(&mut self.trussed, constants::U2F_UP_TIMEOUT)
|
||||
self.up
|
||||
.user_present(&mut self.trussed, constants::U2F_UP_TIMEOUT)
|
||||
.map_err(|_| Error::ConditionsOfUseNotSatisfied)?;
|
||||
0x01
|
||||
},
|
||||
}
|
||||
ControlByte::DontEnforceUserPresenceAndSign => 0x00,
|
||||
};
|
||||
|
||||
@@ -189,14 +198,18 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
|
||||
let key = match &cred.key {
|
||||
Key::WrappedKey(bytes) => {
|
||||
let wrapping_key = self.state.persistent.key_wrapping_key(&mut self.trussed)
|
||||
let wrapping_key = self
|
||||
.state
|
||||
.persistent
|
||||
.key_wrapping_key(&mut self.trussed)
|
||||
.map_err(|_| Error::IncorrectDataParameter)?;
|
||||
let key_result = syscall!(self.trussed.unwrap_key_chacha8poly1305(
|
||||
wrapping_key,
|
||||
bytes,
|
||||
b"",
|
||||
Location::Volatile,
|
||||
)).key;
|
||||
))
|
||||
.key;
|
||||
match key_result {
|
||||
Some(key) => {
|
||||
info!("loaded u2f key!");
|
||||
@@ -216,22 +229,30 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
return Err(Error::IncorrectDataParameter);
|
||||
}
|
||||
|
||||
let sig_count = self.state.persistent.timestamp(&mut self.trussed).
|
||||
map_err(|_| Error::UnspecifiedNonpersistentExecutionError)?;
|
||||
let sig_count = self
|
||||
.state
|
||||
.persistent
|
||||
.timestamp(&mut self.trussed)
|
||||
.map_err(|_| Error::UnspecifiedNonpersistentExecutionError)?;
|
||||
|
||||
let mut commitment = Commitment::new();
|
||||
|
||||
commitment.extend_from_slice(&auth.app_id).unwrap();
|
||||
commitment.push(user_presence_byte).unwrap();
|
||||
commitment.extend_from_slice(&sig_count.to_be_bytes()).unwrap();
|
||||
commitment
|
||||
.extend_from_slice(&sig_count.to_be_bytes())
|
||||
.unwrap();
|
||||
commitment.extend_from_slice(&auth.challenge).unwrap();
|
||||
|
||||
let signature = syscall!(
|
||||
self.trussed.sign(Mechanism::P256,
|
||||
let signature = syscall!(self.trussed.sign(
|
||||
Mechanism::P256,
|
||||
key,
|
||||
&commitment,
|
||||
SignatureSerialization::Asn1Der
|
||||
)).signature.to_bytes().unwrap();
|
||||
))
|
||||
.signature
|
||||
.to_bytes()
|
||||
.unwrap();
|
||||
|
||||
Ok(authenticate::Response {
|
||||
user_presence: user_presence_byte,
|
||||
@@ -239,6 +260,4 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
signature,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+402
-296
File diff suppressed because it is too large
Load Diff
+131
-136
@@ -4,45 +4,35 @@ use core::convert::TryFrom;
|
||||
|
||||
use trussed::{
|
||||
syscall,
|
||||
types::{
|
||||
DirEntry,
|
||||
Location,
|
||||
},
|
||||
types::{DirEntry, Location},
|
||||
};
|
||||
|
||||
use ctap_types::{
|
||||
heapless_bytes::Bytes,
|
||||
Error,
|
||||
ctap2::credential_management::{
|
||||
CredentialProtectionPolicy,
|
||||
Response,
|
||||
},
|
||||
cose::PublicKey,
|
||||
ctap2::credential_management::{CredentialProtectionPolicy, Response},
|
||||
heapless_bytes::Bytes,
|
||||
webauthn::PublicKeyCredentialDescriptor,
|
||||
Error,
|
||||
};
|
||||
|
||||
use littlefs2::path::{Path, PathBuf};
|
||||
|
||||
use crate::{
|
||||
Authenticator,
|
||||
Result,
|
||||
UserPresence,
|
||||
credential::Credential,
|
||||
state::{
|
||||
CredentialManagementEnumerateRps,
|
||||
CredentialManagementEnumerateCredentials,
|
||||
},
|
||||
TrussedRequirements,
|
||||
state::{CredentialManagementEnumerateCredentials, CredentialManagementEnumerateRps},
|
||||
Authenticator, Result, TrussedRequirements, UserPresence,
|
||||
};
|
||||
|
||||
pub(crate) struct CredentialManagement<'a, UP, T>
|
||||
where UP: UserPresence,
|
||||
where
|
||||
UP: UserPresence,
|
||||
{
|
||||
authnr: &'a mut Authenticator<UP, T>,
|
||||
}
|
||||
|
||||
impl<UP, T> core::ops::Deref for CredentialManagement<'_, UP, T>
|
||||
where UP: UserPresence,
|
||||
where
|
||||
UP: UserPresence,
|
||||
{
|
||||
type Target = Authenticator<UP, T>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -51,7 +41,8 @@ where UP: UserPresence,
|
||||
}
|
||||
|
||||
impl<UP, T> core::ops::DerefMut for CredentialManagement<'_, UP, T>
|
||||
where UP: UserPresence,
|
||||
where
|
||||
UP: UserPresence,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.authnr
|
||||
@@ -59,7 +50,8 @@ where UP: UserPresence,
|
||||
}
|
||||
|
||||
impl<'a, UP, T> CredentialManagement<'a, UP, T>
|
||||
where UP: UserPresence,
|
||||
where
|
||||
UP: UserPresence,
|
||||
{
|
||||
pub fn new(authnr: &'a mut Authenticator<UP, T>) -> Self {
|
||||
Self { authnr }
|
||||
@@ -67,25 +59,26 @@ where UP: UserPresence,
|
||||
}
|
||||
|
||||
impl<UP, T> CredentialManagement<'_, UP, T>
|
||||
where UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
where
|
||||
UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
{
|
||||
pub fn get_creds_metadata(&mut self) -> Result<Response> {
|
||||
info!("get metadata");
|
||||
let mut response: Response =
|
||||
Default::default();
|
||||
let mut response: Response = Default::default();
|
||||
|
||||
let guesstimate = self.state.persistent
|
||||
.max_resident_credentials_guesstimate();
|
||||
let guesstimate = self.state.persistent.max_resident_credentials_guesstimate();
|
||||
response.existing_resident_credentials_count = Some(0);
|
||||
response.max_possible_remaining_residential_credentials_count =
|
||||
Some(guesstimate);
|
||||
response.max_possible_remaining_residential_credentials_count = Some(guesstimate);
|
||||
|
||||
let dir = PathBuf::from(b"rk");
|
||||
let maybe_first_rp = syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal, dir.clone(), None)).entry;
|
||||
let maybe_first_rp =
|
||||
syscall!(self
|
||||
.trussed
|
||||
.read_dir_first(Location::Internal, dir.clone(), None))
|
||||
.entry;
|
||||
|
||||
let first_rp = match maybe_first_rp{
|
||||
let first_rp = match maybe_first_rp {
|
||||
None => return Ok(response),
|
||||
Some(rp) => rp,
|
||||
};
|
||||
@@ -94,17 +87,16 @@ where UP: UserPresence,
|
||||
let mut last_rp = PathBuf::from(first_rp.file_name());
|
||||
|
||||
loop {
|
||||
syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal,
|
||||
dir.clone(),
|
||||
Some(last_rp),
|
||||
)).entry.unwrap();
|
||||
syscall!(self
|
||||
.trussed
|
||||
.read_dir_first(Location::Internal, dir.clone(), Some(last_rp),))
|
||||
.entry
|
||||
.unwrap();
|
||||
let maybe_next_rp = syscall!(self.trussed.read_dir_next()).entry;
|
||||
|
||||
match maybe_next_rp {
|
||||
None => {
|
||||
response.existing_resident_credentials_count =
|
||||
Some(num_rks);
|
||||
response.existing_resident_credentials_count = Some(num_rks);
|
||||
response.max_possible_remaining_residential_credentials_count =
|
||||
Some(if num_rks >= guesstimate {
|
||||
0
|
||||
@@ -116,8 +108,7 @@ where UP: UserPresence,
|
||||
Some(rp) => {
|
||||
last_rp = PathBuf::from(rp.file_name());
|
||||
info!("counting..");
|
||||
let (this_rp_rk_count, _) =
|
||||
self.count_rp_rks(PathBuf::from(rp.path()))?;
|
||||
let (this_rp_rk_count, _) = self.count_rp_rks(PathBuf::from(rp.path()))?;
|
||||
info!("{:?}", this_rp_rk_count);
|
||||
num_rks += this_rp_rk_count;
|
||||
}
|
||||
@@ -136,16 +127,15 @@ where UP: UserPresence,
|
||||
|
||||
let dir = PathBuf::from(b"rk");
|
||||
|
||||
let maybe_first_rp = syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal, dir, None)).entry;
|
||||
let maybe_first_rp =
|
||||
syscall!(self.trussed.read_dir_first(Location::Internal, dir, None)).entry;
|
||||
|
||||
response.total_rps = Some(match maybe_first_rp {
|
||||
None => 0,
|
||||
_ => {
|
||||
let mut num_rps = 1;
|
||||
loop {
|
||||
let maybe_next_rp = syscall!(self.trussed.read_dir_next())
|
||||
.entry;
|
||||
let maybe_next_rp = syscall!(self.trussed.read_dir_next()).entry;
|
||||
match maybe_next_rp {
|
||||
None => break,
|
||||
_ => num_rps += 1,
|
||||
@@ -156,21 +146,21 @@ where UP: UserPresence,
|
||||
});
|
||||
|
||||
if let Some(rp) = maybe_first_rp {
|
||||
|
||||
// load credential and extract rp and rpIdHash
|
||||
let maybe_first_credential = syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal,
|
||||
PathBuf::from(rp.path()),
|
||||
None
|
||||
)).entry;
|
||||
))
|
||||
.entry;
|
||||
|
||||
match maybe_first_credential {
|
||||
None => panic!("chaos! disorder!"),
|
||||
Some(rk_entry) => {
|
||||
let serialized = syscall!(self.trussed.read_file(
|
||||
Location::Internal,
|
||||
rk_entry.path().into(),
|
||||
)).data;
|
||||
let serialized = syscall!(self
|
||||
.trussed
|
||||
.read_file(Location::Internal, rk_entry.path().into(),))
|
||||
.data;
|
||||
|
||||
let credential = Credential::deserialize(&serialized)
|
||||
// this may be a confusing error message
|
||||
@@ -180,7 +170,6 @@ where UP: UserPresence,
|
||||
|
||||
response.rp_id_hash = Some(self.hash(rp.id.as_ref()));
|
||||
response.rp = Some(rp);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,11 +177,10 @@ where UP: UserPresence,
|
||||
if let Some(total_rps) = response.total_rps {
|
||||
if total_rps > 1 {
|
||||
let rp_id_hash = response.rp_id_hash.as_ref().unwrap().clone();
|
||||
self.state.runtime.cached_rp = Some(
|
||||
CredentialManagementEnumerateRps {
|
||||
remaining: total_rps - 1,
|
||||
rp_id_hash,
|
||||
});
|
||||
self.state.runtime.cached_rp = Some(CredentialManagementEnumerateRps {
|
||||
remaining: total_rps - 1,
|
||||
rp_id_hash,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +194,12 @@ where UP: UserPresence,
|
||||
let CredentialManagementEnumerateRps {
|
||||
remaining,
|
||||
rp_id_hash: last_rp_id_hash,
|
||||
} = self.state.runtime.cached_rp.clone().ok_or(Error::NotAllowed)?;
|
||||
} = self
|
||||
.state
|
||||
.runtime
|
||||
.cached_rp
|
||||
.clone()
|
||||
.ok_or(Error::NotAllowed)?;
|
||||
|
||||
let dir = PathBuf::from(b"rk");
|
||||
|
||||
@@ -214,11 +207,11 @@ where UP: UserPresence,
|
||||
super::format_hex(&last_rp_id_hash[..8], &mut hex);
|
||||
let filename = PathBuf::from(&hex);
|
||||
|
||||
let mut maybe_next_rp = syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal,
|
||||
dir,
|
||||
Some(filename),
|
||||
)).entry;
|
||||
let mut maybe_next_rp =
|
||||
syscall!(self
|
||||
.trussed
|
||||
.read_dir_first(Location::Internal, dir, Some(filename),))
|
||||
.entry;
|
||||
|
||||
// Advance to the next
|
||||
if maybe_next_rp.is_some() {
|
||||
@@ -235,15 +228,16 @@ where UP: UserPresence,
|
||||
Location::Internal,
|
||||
PathBuf::from(rp.path()),
|
||||
None
|
||||
)).entry;
|
||||
))
|
||||
.entry;
|
||||
|
||||
match maybe_first_credential {
|
||||
None => panic!("chaos! disorder!"),
|
||||
Some(rk_entry) => {
|
||||
let serialized = syscall!(self.trussed.read_file(
|
||||
Location::Internal,
|
||||
rk_entry.path().into(),
|
||||
)).data;
|
||||
let serialized = syscall!(self
|
||||
.trussed
|
||||
.read_file(Location::Internal, rk_entry.path().into(),))
|
||||
.data;
|
||||
|
||||
let credential = Credential::deserialize(&serialized)
|
||||
// this may be a confusing error message
|
||||
@@ -274,11 +268,11 @@ where UP: UserPresence,
|
||||
}
|
||||
|
||||
fn count_rp_rks(&mut self, rp_dir: PathBuf) -> Result<(u32, DirEntry)> {
|
||||
let maybe_first_rk = syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal,
|
||||
rp_dir,
|
||||
None
|
||||
)).entry;
|
||||
let maybe_first_rk =
|
||||
syscall!(self
|
||||
.trussed
|
||||
.read_dir_first(Location::Internal, rp_dir, None))
|
||||
.entry;
|
||||
|
||||
let first_rk = maybe_first_rk.ok_or(Error::NoCredentials)?;
|
||||
|
||||
@@ -302,8 +296,7 @@ where UP: UserPresence,
|
||||
let (num_rks, first_rk) = self.count_rp_rks(rp_dir)?;
|
||||
|
||||
// extract data required into response
|
||||
let mut response = self.extract_response_from_credential_file(
|
||||
first_rk.path())?;
|
||||
let mut response = self.extract_response_from_credential_file(first_rk.path())?;
|
||||
response.total_credentials = Some(num_rks);
|
||||
|
||||
// cache state for next call
|
||||
@@ -328,7 +321,12 @@ where UP: UserPresence,
|
||||
remaining,
|
||||
rp_dir,
|
||||
prev_filename,
|
||||
} = self.state.runtime.cached_rk.clone().ok_or(Error::NotAllowed)?;
|
||||
} = self
|
||||
.state
|
||||
.runtime
|
||||
.cached_rk
|
||||
.clone()
|
||||
.ok_or(Error::NotAllowed)?;
|
||||
// let (remaining, rp_dir, prev_filename) = match self.state.runtime.cached_rk {
|
||||
// Some(CredentialManagementEnumerateCredentials(
|
||||
// x, ref y, ref z))
|
||||
@@ -342,11 +340,11 @@ where UP: UserPresence,
|
||||
// super::format_hex(&rp_id_hash[..8], &mut hex);
|
||||
// let rp_dir = PathBuf::from(b"rk").join(&PathBuf::from(&hex));
|
||||
|
||||
let mut maybe_next_rk = syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal,
|
||||
rp_dir,
|
||||
Some(prev_filename)
|
||||
)).entry;
|
||||
let mut maybe_next_rk =
|
||||
syscall!(self
|
||||
.trussed
|
||||
.read_dir_first(Location::Internal, rp_dir, Some(prev_filename)))
|
||||
.entry;
|
||||
|
||||
// Advance to the next
|
||||
if maybe_next_rk.is_some() {
|
||||
@@ -358,8 +356,7 @@ where UP: UserPresence,
|
||||
match maybe_next_rk {
|
||||
Some(rk) => {
|
||||
// extract data required into response
|
||||
let response = self.extract_response_from_credential_file(
|
||||
rk.path())?;
|
||||
let response = self.extract_response_from_credential_file(rk.path())?;
|
||||
|
||||
// cache state for next call
|
||||
if remaining > 1 {
|
||||
@@ -376,19 +373,14 @@ where UP: UserPresence,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn extract_response_from_credential_file(&mut self, rk_path: &Path) -> Result<Response> {
|
||||
|
||||
// user (0x06)
|
||||
// credentialID (0x07): PublicKeyCredentialDescriptor
|
||||
// publicKey (0x08): public key of the credential in COSE_Key format
|
||||
// totalCredentials (0x09): total number of credentials for this RP
|
||||
// credProtect (0x0A): credential protection policy
|
||||
|
||||
let serialized = syscall!(self.trussed.read_file(
|
||||
Location::Internal,
|
||||
rk_path.into(),
|
||||
)).data;
|
||||
let serialized = syscall!(self.trussed.read_file(Location::Internal, rk_path.into(),)).data;
|
||||
|
||||
let credential = Credential::deserialize(&serialized)
|
||||
// this may be a confusing error message
|
||||
@@ -398,7 +390,10 @@ where UP: UserPresence,
|
||||
|
||||
// why these contortions to get kek. sheesh
|
||||
let authnr = &mut self.authnr;
|
||||
let kek = authnr.state.persistent.key_encryption_key(&mut authnr.trussed)?;
|
||||
let kek = authnr
|
||||
.state
|
||||
.persistent
|
||||
.key_encryption_key(&mut authnr.trussed)?;
|
||||
|
||||
let credential_id = credential.id(&mut self.trussed, kek, None)?;
|
||||
|
||||
@@ -412,33 +407,38 @@ where UP: UserPresence,
|
||||
use trussed::types::{KeySerialization, Mechanism};
|
||||
|
||||
let algorithm = SigningAlgorithm::try_from(credential.algorithm)?;
|
||||
let cose_public_key = match algorithm {
|
||||
let cose_public_key = match algorithm {
|
||||
SigningAlgorithm::P256 => {
|
||||
let public_key = syscall!(self.trussed.derive_p256_public_key(private_key, Location::Volatile)).key;
|
||||
let public_key = syscall!(self
|
||||
.trussed
|
||||
.derive_p256_public_key(private_key, Location::Volatile))
|
||||
.key;
|
||||
let cose_public_key = syscall!(self.trussed.serialize_key(
|
||||
Mechanism::P256, public_key,
|
||||
Mechanism::P256,
|
||||
public_key,
|
||||
// KeySerialization::EcdhEsHkdf256
|
||||
KeySerialization::Cose,
|
||||
)).serialized_key;
|
||||
))
|
||||
.serialized_key;
|
||||
syscall!(self.trussed.delete(public_key));
|
||||
PublicKey::P256Key(
|
||||
ctap_types::serde::cbor_deserialize(&cose_public_key)
|
||||
.unwrap())
|
||||
PublicKey::P256Key(ctap_types::serde::cbor_deserialize(&cose_public_key).unwrap())
|
||||
}
|
||||
SigningAlgorithm::Ed25519 => {
|
||||
let public_key = syscall!(self.trussed.derive_ed255_public_key(
|
||||
private_key, Location::Volatile)).key;
|
||||
let cose_public_key = syscall!(self.trussed.serialize_ed255_key(
|
||||
public_key, KeySerialization::Cose
|
||||
)).serialized_key;
|
||||
let public_key = syscall!(self
|
||||
.trussed
|
||||
.derive_ed255_public_key(private_key, Location::Volatile))
|
||||
.key;
|
||||
let cose_public_key = syscall!(self
|
||||
.trussed
|
||||
.serialize_ed255_key(public_key, KeySerialization::Cose))
|
||||
.serialized_key;
|
||||
syscall!(self.trussed.delete(public_key));
|
||||
PublicKey::Ed25519Key(
|
||||
ctap_types::serde::cbor_deserialize(&cose_public_key)
|
||||
.unwrap())
|
||||
}
|
||||
// SigningAlgorithm::Totp => {
|
||||
// PublicKey::TotpKey(Default::default())
|
||||
// }
|
||||
ctap_types::serde::cbor_deserialize(&cose_public_key).unwrap(),
|
||||
)
|
||||
} // SigningAlgorithm::Totp => {
|
||||
// PublicKey::TotpKey(Default::default())
|
||||
// }
|
||||
};
|
||||
let cred_protect = match credential.cred_protect {
|
||||
Some(x) => Some(x),
|
||||
@@ -456,11 +456,10 @@ where UP: UserPresence,
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn delete_credential(&mut self,
|
||||
pub fn delete_credential(
|
||||
&mut self,
|
||||
credential_descriptor: &PublicKeyCredentialDescriptor,
|
||||
)
|
||||
-> Result<Response>
|
||||
{
|
||||
) -> Result<Response> {
|
||||
info!("delete credential");
|
||||
let credential_id_hash = self.hash(&credential_descriptor.id[..]);
|
||||
let mut hex = [b'0'; 16];
|
||||
@@ -468,43 +467,39 @@ where UP: UserPresence,
|
||||
let dir = PathBuf::from(b"rk");
|
||||
let filename = PathBuf::from(&hex);
|
||||
|
||||
let rk_path = syscall!(self.trussed.locate_file(
|
||||
Location::Internal,
|
||||
Some(dir),
|
||||
filename,
|
||||
)).path.ok_or(Error::InvalidCredential)?;
|
||||
|
||||
let rk_path = syscall!(self
|
||||
.trussed
|
||||
.locate_file(Location::Internal, Some(dir), filename,))
|
||||
.path
|
||||
.ok_or(Error::InvalidCredential)?;
|
||||
|
||||
// DELETE
|
||||
self.delete_resident_key_by_path(&rk_path)?;
|
||||
|
||||
// get rid of directory if it's now empty
|
||||
let rp_path = rk_path.parent()
|
||||
let rp_path = rk_path
|
||||
.parent()
|
||||
// by construction, RK has a parent, its RP
|
||||
.unwrap();
|
||||
|
||||
let maybe_first_remaining_rk = syscall!(self.trussed.read_dir_first(
|
||||
Location::Internal,
|
||||
rp_path.clone(),
|
||||
None,
|
||||
)).entry;
|
||||
let maybe_first_remaining_rk =
|
||||
syscall!(self
|
||||
.trussed
|
||||
.read_dir_first(Location::Internal, rp_path.clone(), None,))
|
||||
.entry;
|
||||
|
||||
if maybe_first_remaining_rk.is_none() {
|
||||
info!("deleting parent {:?} as this was its last RK",
|
||||
&rp_path);
|
||||
syscall!(self.trussed.remove_dir(
|
||||
Location::Internal,
|
||||
rp_path,
|
||||
));
|
||||
info!("deleting parent {:?} as this was its last RK", &rp_path);
|
||||
syscall!(self.trussed.remove_dir(Location::Internal, rp_path,));
|
||||
} else {
|
||||
info!("not deleting deleting parent {:?} as there is {:?}",
|
||||
&rp_path,
|
||||
&maybe_first_remaining_rk.unwrap().path(),
|
||||
);
|
||||
info!(
|
||||
"not deleting deleting parent {:?} as there is {:?}",
|
||||
&rp_path,
|
||||
&maybe_first_remaining_rk.unwrap().path(),
|
||||
);
|
||||
}
|
||||
// just return OK
|
||||
let response = Default::default();
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+61
-26
@@ -3,30 +3,36 @@
|
||||
mod apdu;
|
||||
mod ctaphid;
|
||||
|
||||
use crate::{Authenticator, TrussedRequirements, UserPresence};
|
||||
#[allow(unused_imports)]
|
||||
use crate::msp;
|
||||
use crate::{Authenticator, TrussedRequirements, UserPresence};
|
||||
|
||||
use ctap_types::{ctap1, ctap2};
|
||||
use iso7816::Status;
|
||||
|
||||
impl<UP, T> iso7816::App for Authenticator<UP, T>
|
||||
where UP: UserPresence,
|
||||
where
|
||||
UP: UserPresence,
|
||||
{
|
||||
fn aid(&self) -> iso7816::Aid {
|
||||
iso7816::Aid::new(&[ 0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01])
|
||||
iso7816::Aid::new(&[0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[inline(never)]
|
||||
/// Deserialize U2F, call authenticator, serialize response *Result*.
|
||||
fn handle_ctap1<T, UP>(authenticator: &mut Authenticator<UP, T>, data: &[u8], response: &mut apdu_dispatch::response::Data)
|
||||
where
|
||||
fn handle_ctap1<T, UP>(
|
||||
authenticator: &mut Authenticator<UP, T>,
|
||||
data: &[u8],
|
||||
response: &mut apdu_dispatch::response::Data,
|
||||
) where
|
||||
T: TrussedRequirements,
|
||||
UP: UserPresence,
|
||||
{
|
||||
debug_now!("handle CTAP1: remaining stack: {} bytes", msp() - 0x2000_0000);
|
||||
debug_now!(
|
||||
"handle CTAP1: remaining stack: {} bytes",
|
||||
msp() - 0x2000_0000
|
||||
);
|
||||
// debug_now!("1A SP: {:X}", msp());
|
||||
match try_handle_ctap1(authenticator, data, response) {
|
||||
Ok(()) => {
|
||||
@@ -34,12 +40,12 @@ where
|
||||
// Need to add x9000 success code (normally the apdu-dispatch does this, but
|
||||
// since u2f uses apdus over ctaphid, we must do it here.)
|
||||
response.extend_from_slice(&[0x90, 0x00]).ok();
|
||||
},
|
||||
}
|
||||
Err(status) => {
|
||||
let code: [u8; 2] = status.into();
|
||||
debug_now!("CTAP1 error: {:?} ({})", status, hex_str!(&code));
|
||||
response.extend_from_slice(&code).ok();
|
||||
},
|
||||
}
|
||||
}
|
||||
// debug_now!("1B SP: {:X}", msp());
|
||||
debug_now!("end handle CTAP1");
|
||||
@@ -47,12 +53,18 @@ where
|
||||
|
||||
#[inline(never)]
|
||||
/// Deserialize CBOR, call authenticator, serialize response *Result*.
|
||||
fn handle_ctap2<T, UP>(authenticator: &mut Authenticator<UP, T>, data: &[u8], response: &mut apdu_dispatch::response::Data)
|
||||
where
|
||||
fn handle_ctap2<T, UP>(
|
||||
authenticator: &mut Authenticator<UP, T>,
|
||||
data: &[u8],
|
||||
response: &mut apdu_dispatch::response::Data,
|
||||
) where
|
||||
T: TrussedRequirements,
|
||||
UP: UserPresence,
|
||||
{
|
||||
debug_now!("handle CTAP2: remaining stack: {} bytes", msp() - 0x2000_0000);
|
||||
debug_now!(
|
||||
"handle CTAP2: remaining stack: {} bytes",
|
||||
msp() - 0x2000_0000
|
||||
);
|
||||
// debug_now!("2A SP: {:X}", msp());
|
||||
if let Err(error) = try_handle_ctap2(authenticator, data, response) {
|
||||
debug_now!("CTAP2 error: {:02X}", error);
|
||||
@@ -63,14 +75,20 @@ where
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn try_handle_ctap1<T, UP>(authenticator: &mut Authenticator<UP, T>, data: &[u8], response: &mut apdu_dispatch::response::Data)
|
||||
-> Result<(), Status>
|
||||
fn try_handle_ctap1<T, UP>(
|
||||
authenticator: &mut Authenticator<UP, T>,
|
||||
data: &[u8],
|
||||
response: &mut apdu_dispatch::response::Data,
|
||||
) -> Result<(), Status>
|
||||
where
|
||||
T: TrussedRequirements,
|
||||
UP: UserPresence,
|
||||
{
|
||||
// Annoyance: We can't load in fido-authenticator constructor.
|
||||
authenticator.state.persistent.load_if_not_initialised(&mut authenticator.trussed);
|
||||
authenticator
|
||||
.state
|
||||
.persistent
|
||||
.load_if_not_initialised(&mut authenticator.trussed);
|
||||
|
||||
// let command = apdu_dispatch::Command::try_from(data)
|
||||
// .map_err(|_| Status::IncorrectDataParameter)?;
|
||||
@@ -97,16 +115,25 @@ where
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn try_handle_ctap2<T, UP>(authenticator: &mut Authenticator<UP, T>, data: &[u8], response: &mut apdu_dispatch::response::Data)
|
||||
-> Result<(), u8>
|
||||
fn try_handle_ctap2<T, UP>(
|
||||
authenticator: &mut Authenticator<UP, T>,
|
||||
data: &[u8],
|
||||
response: &mut apdu_dispatch::response::Data,
|
||||
) -> Result<(), u8>
|
||||
where
|
||||
T: TrussedRequirements,
|
||||
UP: UserPresence,
|
||||
{
|
||||
// Annoyance: We can't load in fido-authenticator constructor.
|
||||
authenticator.state.persistent.load_if_not_initialised(&mut authenticator.trussed);
|
||||
authenticator
|
||||
.state
|
||||
.persistent
|
||||
.load_if_not_initialised(&mut authenticator.trussed);
|
||||
|
||||
debug_now!("try_handle CTAP2: remaining stack: {} bytes", msp() - 0x2000_0000);
|
||||
debug_now!(
|
||||
"try_handle CTAP2: remaining stack: {} bytes",
|
||||
msp() - 0x2000_0000
|
||||
);
|
||||
|
||||
// let ctap_request = ctap2::Request::deserialize(data)
|
||||
// .map_err(|error| error as u8)?;
|
||||
@@ -120,22 +147,30 @@ where
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn try_get_ctap2_response<T, UP>(authenticator: &mut Authenticator<UP, T>, data: &[u8])
|
||||
-> Result<ctap2::Response, u8>
|
||||
fn try_get_ctap2_response<T, UP>(
|
||||
authenticator: &mut Authenticator<UP, T>,
|
||||
data: &[u8],
|
||||
) -> Result<ctap2::Response, u8>
|
||||
where
|
||||
T: TrussedRequirements,
|
||||
UP: UserPresence,
|
||||
{
|
||||
// Annoyance: We can't load in fido-authenticator constructor.
|
||||
authenticator.state.persistent.load_if_not_initialised(&mut authenticator.trussed);
|
||||
authenticator
|
||||
.state
|
||||
.persistent
|
||||
.load_if_not_initialised(&mut authenticator.trussed);
|
||||
|
||||
debug_now!("try_get CTAP2: remaining stack: {} bytes", msp() - 0x2000_0000);
|
||||
debug_now!(
|
||||
"try_get CTAP2: remaining stack: {} bytes",
|
||||
msp() - 0x2000_0000
|
||||
);
|
||||
|
||||
// Goal of these nested scopes is to keep stack small.
|
||||
let ctap_request = ctap2::Request::deserialize(data)
|
||||
.map_err(|error| error as u8)?;
|
||||
let ctap_request = ctap2::Request::deserialize(data).map_err(|error| error as u8)?;
|
||||
debug_now!("2a SP: {:X}", msp());
|
||||
use ctap2::Authenticator;
|
||||
authenticator.call_ctap2(&ctap_request)
|
||||
authenticator
|
||||
.call_ctap2(&ctap_request)
|
||||
.map_err(|error| error as u8)
|
||||
}
|
||||
|
||||
+19
-23
@@ -1,9 +1,6 @@
|
||||
use apdu_dispatch::{Command, response::Data, app as apdu};
|
||||
use apdu_dispatch::{app as apdu, response::Data, Command};
|
||||
use ctap_types::{serde::error::Error as SerdeError, Error};
|
||||
use ctaphid_dispatch::app as ctaphid;
|
||||
use ctap_types::{
|
||||
Error,
|
||||
serde::error::Error as SerdeError,
|
||||
};
|
||||
use iso7816::Status;
|
||||
|
||||
use crate::{Authenticator, TrussedRequirements, UserPresence};
|
||||
@@ -16,26 +13,21 @@ pub enum CtapMappingError {
|
||||
impl From<CtapMappingError> for Error {
|
||||
fn from(mapping_error: CtapMappingError) -> Error {
|
||||
match mapping_error {
|
||||
CtapMappingError::InvalidCommand(_cmd) => {
|
||||
Error::InvalidCommand
|
||||
}
|
||||
CtapMappingError::ParsingError(cbor_error) => {
|
||||
match cbor_error {
|
||||
SerdeError::SerdeMissingField => Error::MissingParameter,
|
||||
_ => Error::InvalidCbor
|
||||
}
|
||||
}
|
||||
CtapMappingError::InvalidCommand(_cmd) => Error::InvalidCommand,
|
||||
CtapMappingError::ParsingError(cbor_error) => match cbor_error {
|
||||
SerdeError::SerdeMissingField => Error::MissingParameter,
|
||||
_ => Error::InvalidCbor,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl<UP, T> apdu::App<{apdu_dispatch::command::SIZE}, {apdu_dispatch::response::SIZE} >
|
||||
for Authenticator<UP, T>
|
||||
where UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
impl<UP, T> apdu::App<{ apdu_dispatch::command::SIZE }, { apdu_dispatch::response::SIZE }>
|
||||
for Authenticator<UP, T>
|
||||
where
|
||||
UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
{
|
||||
|
||||
fn select(&mut self, _: &Command, reply: &mut Data) -> apdu::Result {
|
||||
reply.extend_from_slice(b"U2F_V2").unwrap();
|
||||
Ok(())
|
||||
@@ -43,7 +35,12 @@ for Authenticator<UP, T>
|
||||
|
||||
fn deselect(&mut self) {}
|
||||
|
||||
fn call(&mut self, interface: apdu::Interface, apdu: &Command, response: &mut Data) -> apdu::Result {
|
||||
fn call(
|
||||
&mut self,
|
||||
interface: apdu::Interface,
|
||||
apdu: &Command,
|
||||
response: &mut Data,
|
||||
) -> apdu::Result {
|
||||
// FIDO-over-CCID does not seem to officially be a thing; we don't support it.
|
||||
// If we would, need to review the following cases catering to semi-documented U2F legacy.
|
||||
if interface != apdu::Interface::Contactless {
|
||||
@@ -62,7 +59,7 @@ for Authenticator<UP, T>
|
||||
Ok(match instruction {
|
||||
// U2F instruction codes
|
||||
// NB(nickray): I don't think 0x00 is a valid case.
|
||||
0x00 | 0x01 | 0x02 => super::handle_ctap1(self, apdu.data(), response),//self.call_authenticator_u2f(apdu, response),
|
||||
0x00 | 0x01 | 0x02 => super::handle_ctap1(self, apdu.data(), response), //self.call_authenticator_u2f(apdu, response),
|
||||
|
||||
_ => {
|
||||
match ctaphid::Command::try_from(instruction) {
|
||||
@@ -79,4 +76,3 @@ for Authenticator<UP, T>
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-10
@@ -1,22 +1,29 @@
|
||||
use ctaphid_dispatch::app as ctaphid;
|
||||
|
||||
use crate::{Authenticator, TrussedRequirements, UserPresence};
|
||||
#[allow(unused_imports)]
|
||||
use crate::msp;
|
||||
use crate::{Authenticator, TrussedRequirements, UserPresence};
|
||||
|
||||
impl<UP, T> ctaphid::App for Authenticator<UP, T>
|
||||
where UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
where
|
||||
UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
{
|
||||
|
||||
fn commands(&self,) -> &'static [ctaphid::Command] {
|
||||
&[ ctaphid::Command::Cbor, ctaphid::Command::Msg ]
|
||||
fn commands(&self) -> &'static [ctaphid::Command] {
|
||||
&[ctaphid::Command::Cbor, ctaphid::Command::Msg]
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn call(&mut self, command: ctaphid::Command, request: &ctaphid::Message, response: &mut ctaphid::Message) -> ctaphid::AppResult {
|
||||
|
||||
debug_now!("ctaphid-dispatch: remaining stack: {} bytes", msp() - 0x2000_0000);
|
||||
fn call(
|
||||
&mut self,
|
||||
command: ctaphid::Command,
|
||||
request: &ctaphid::Message,
|
||||
response: &mut ctaphid::Message,
|
||||
) -> ctaphid::AppResult {
|
||||
debug_now!(
|
||||
"ctaphid-dispatch: remaining stack: {} bytes",
|
||||
msp() - 0x2000_0000
|
||||
);
|
||||
|
||||
if request.len() < 1 {
|
||||
debug_now!("invalid request length in ctaphid.call");
|
||||
@@ -26,7 +33,6 @@ where UP: UserPresence,
|
||||
// info_now!("request: ");
|
||||
// blocking::dump_hex(request, request.len());
|
||||
Ok(match command {
|
||||
|
||||
ctaphid::Command::Cbor => super::handle_ctap2(self, request, response),
|
||||
ctaphid::Command::Msg => super::handle_ctap1(self, request, response),
|
||||
_ => {
|
||||
|
||||
+48
-38
@@ -15,17 +15,11 @@
|
||||
extern crate delog;
|
||||
generate_macros!();
|
||||
|
||||
use trussed::{
|
||||
client, syscall,
|
||||
Client as TrussedClient,
|
||||
types::{
|
||||
Message,
|
||||
},
|
||||
};
|
||||
use trussed::{client, syscall, types::Message, Client as TrussedClient};
|
||||
|
||||
use ctap_types::{
|
||||
heapless_bytes::Bytes,
|
||||
authenticator::{Request, Response},
|
||||
heapless_bytes::Bytes,
|
||||
};
|
||||
|
||||
/// Re-export of `ctap-types` authenticator errors.
|
||||
@@ -34,7 +28,7 @@ pub use ctap_types::Error;
|
||||
mod ctap1;
|
||||
mod ctap2;
|
||||
|
||||
#[cfg(feature="dispatch")]
|
||||
#[cfg(feature = "dispatch")]
|
||||
mod dispatch;
|
||||
|
||||
pub mod constants;
|
||||
@@ -44,7 +38,6 @@ pub mod state;
|
||||
/// Results with our [`Error`].
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
|
||||
/// Trait bound on our implementation's requirements from a Trussed client.
|
||||
///
|
||||
/// - Client is core Trussed client functionality.
|
||||
@@ -58,21 +51,20 @@ pub trait TrussedRequirements:
|
||||
+ client::Aes256Cbc
|
||||
+ client::Sha256
|
||||
+ client::HmacSha256
|
||||
+ client::Ed255
|
||||
// + client::Totp
|
||||
{}
|
||||
+ client::Ed255 // + client::Totp
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> TrussedRequirements for T
|
||||
where T:
|
||||
client::Client
|
||||
+ client::P256
|
||||
+ client::Chacha8Poly1305
|
||||
+ client::Aes256Cbc
|
||||
+ client::Sha256
|
||||
+ client::HmacSha256
|
||||
+ client::Ed255
|
||||
// + client::Totp
|
||||
{}
|
||||
impl<T> TrussedRequirements for T where
|
||||
T: client::Client
|
||||
+ client::P256
|
||||
+ client::Chacha8Poly1305
|
||||
+ client::Aes256Cbc
|
||||
+ client::Sha256
|
||||
+ client::HmacSha256
|
||||
+ client::Ed255 // + client::Totp
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
/// Externally defined configuration.
|
||||
@@ -140,7 +132,9 @@ fn format_hex(data: &[u8], mut buffer: &mut [u8]) {
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn msp() -> u32 { 0x2000_0000 }
|
||||
pub(crate) fn msp() -> u32 {
|
||||
0x2000_0000
|
||||
}
|
||||
|
||||
/// Currently Ed25519 and P256.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
@@ -169,10 +163,14 @@ impl core::convert::TryFrom<i32> for SigningAlgorithm {
|
||||
|
||||
/// Method to check for user presence.
|
||||
pub trait UserPresence: Copy {
|
||||
fn user_present<T: TrussedClient>(self, trussed: &mut T, timeout_milliseconds: u32) -> Result<()>;
|
||||
fn user_present<T: TrussedClient>(
|
||||
self,
|
||||
trussed: &mut T,
|
||||
timeout_milliseconds: u32,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
#[deprecated(note="use `Silent` directly`")]
|
||||
#[deprecated(note = "use `Silent` directly`")]
|
||||
#[doc(hidden)]
|
||||
pub type SilentAuthenticator = Silent;
|
||||
|
||||
@@ -181,12 +179,12 @@ pub type SilentAuthenticator = Silent;
|
||||
pub struct Silent {}
|
||||
|
||||
impl UserPresence for Silent {
|
||||
fn user_present<T: TrussedClient>(self, _: &mut T, _:u32) -> Result<()> {
|
||||
fn user_present<T: TrussedClient>(self, _: &mut T, _: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated(note="use `Conforming` directly")]
|
||||
#[deprecated(note = "use `Conforming` directly")]
|
||||
#[doc(hidden)]
|
||||
pub type NonSilentAuthenticator = Conforming;
|
||||
|
||||
@@ -195,7 +193,11 @@ pub type NonSilentAuthenticator = Conforming;
|
||||
pub struct Conforming {}
|
||||
|
||||
impl UserPresence for Conforming {
|
||||
fn user_present<T: TrussedClient>(self, trussed: &mut T, timeout_milliseconds: u32) -> Result<()> {
|
||||
fn user_present<T: TrussedClient>(
|
||||
self,
|
||||
trussed: &mut T,
|
||||
timeout_milliseconds: u32,
|
||||
) -> Result<()> {
|
||||
let result = syscall!(trussed.confirm_user_present(timeout_milliseconds)).result;
|
||||
result.map_err(|err| match err {
|
||||
trussed::types::consent::Error::TimedOut => Error::UserActionTimeout,
|
||||
@@ -205,22 +207,31 @@ impl UserPresence for Conforming {
|
||||
}
|
||||
}
|
||||
|
||||
fn cbor_serialize_message<T: serde::Serialize>(object: &T) -> core::result::Result<Message, ctap_types::serde::Error> {
|
||||
fn cbor_serialize_message<T: serde::Serialize>(
|
||||
object: &T,
|
||||
) -> core::result::Result<Message, ctap_types::serde::Error> {
|
||||
trussed::cbor_serialize_bytes(object)
|
||||
}
|
||||
|
||||
impl<UP, T> Authenticator<UP, T>
|
||||
where UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
where
|
||||
UP: UserPresence,
|
||||
T: TrussedRequirements,
|
||||
{
|
||||
pub fn new(trussed: T, up: UP, config: Config) -> Self {
|
||||
|
||||
let state = state::State::new();
|
||||
Self { trussed, state, up, config }
|
||||
Self {
|
||||
trussed,
|
||||
state,
|
||||
up,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&mut self, request: &Request) -> Result<Response> {
|
||||
self.state.persistent.load_if_not_initialised(&mut self.trussed);
|
||||
self.state
|
||||
.persistent
|
||||
.load_if_not_initialised(&mut self.trussed);
|
||||
|
||||
match request {
|
||||
Request::Ctap2(request) => {
|
||||
@@ -245,5 +256,4 @@ where UP: UserPresence,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
}
|
||||
mod test {}
|
||||
|
||||
+99
-72
@@ -2,32 +2,24 @@
|
||||
//!
|
||||
//! Needs cleanup.
|
||||
|
||||
use trussed::{
|
||||
client, syscall, try_syscall,
|
||||
Client as TrussedClient,
|
||||
types::{
|
||||
self,
|
||||
KeyId,
|
||||
Location,
|
||||
Mechanism,
|
||||
},
|
||||
};
|
||||
use ctap_types::{
|
||||
Bytes, String,
|
||||
Error,
|
||||
cose::EcdhEsHkdf256PublicKey as CoseEcdhEsHkdf256PublicKey,
|
||||
// 2022-02-27: 10 credentials
|
||||
sizes::MAX_CREDENTIAL_COUNT_IN_LIST, // U8 currently
|
||||
Bytes,
|
||||
Error,
|
||||
String,
|
||||
};
|
||||
use trussed::{
|
||||
client, syscall, try_syscall,
|
||||
types::{self, KeyId, Location, Mechanism},
|
||||
Client as TrussedClient,
|
||||
};
|
||||
|
||||
use heapless::binary_heap::{BinaryHeap, Max};
|
||||
use littlefs2::path::PathBuf;
|
||||
|
||||
use crate::{
|
||||
cbor_serialize_message,
|
||||
credential::Credential,
|
||||
Result,
|
||||
};
|
||||
use crate::{cbor_serialize_message, credential::Credential, Result};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct CachedCredential {
|
||||
@@ -81,7 +73,6 @@ pub type CredentialCache = CredentialCacheGeneric<MAX_CREDENTIAL_COUNT_IN_LIST>;
|
||||
|
||||
#[derive(Clone, Debug, /*uDebug, Eq, PartialEq,*/ serde::Deserialize, serde::Serialize)]
|
||||
pub struct State {
|
||||
|
||||
/// Batch device identity (aaguid, certificate, key).
|
||||
pub identity: Identity,
|
||||
pub persistent: PersistentState,
|
||||
@@ -95,7 +86,6 @@ impl Default for State {
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
||||
// pub fn new(trussed: &mut TrussedClient) -> Self {
|
||||
pub fn new() -> Self {
|
||||
// let identity = Identity::get(trussed);
|
||||
@@ -104,7 +94,11 @@ impl State {
|
||||
// let persistent = PersistentState::load_or_reset(trussed);
|
||||
let persistent = Default::default();
|
||||
|
||||
Self { identity, persistent, runtime }
|
||||
Self {
|
||||
identity,
|
||||
persistent,
|
||||
runtime,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrement_retries<T: TrussedClient>(&mut self, trussed: &mut T) -> Result<()> {
|
||||
@@ -119,9 +113,7 @@ impl State {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn pin_blocked(&self) -> Result<()> {
|
||||
|
||||
if self.persistent.pin_blocked() {
|
||||
return Err(Error::PinBlocked);
|
||||
}
|
||||
@@ -131,7 +123,6 @@ impl State {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Batch device identity (aaguid, certificate, key).
|
||||
@@ -146,16 +137,13 @@ pub type Aaguid = [u8; 16];
|
||||
pub type Certificate = trussed::types::Message;
|
||||
|
||||
impl Identity {
|
||||
|
||||
// Attempt to yank out the aaguid of a certificate.
|
||||
fn yank_aaguid(&mut self, der: &[u8]) -> Option<[u8; 16]> {
|
||||
|
||||
let aaguid_start_sequence = [
|
||||
// OBJECT IDENTIFIER 1.3.6.1.4.1.45724.1.1.4 (AAGUID)
|
||||
0x06u8, 0x0B, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xE5, 0x1C, 0x01, 0x01, 0x04,
|
||||
|
||||
// Sequence, 16 bytes
|
||||
0x04, 0x12, 0x04, 0x10
|
||||
0x04, 0x12, 0x04, 0x10,
|
||||
];
|
||||
|
||||
// Scan for the beginning sequence for AAGUID.
|
||||
@@ -180,16 +168,16 @@ impl Identity {
|
||||
}
|
||||
|
||||
/// Lookup batch key and certificate, together with AAUGID.
|
||||
pub fn attestation<T: TrussedClient>(&mut self, trussed: &mut T) -> (Option<(KeyId, Certificate)>, Aaguid)
|
||||
{
|
||||
pub fn attestation<T: TrussedClient>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) -> (Option<(KeyId, Certificate)>, Aaguid) {
|
||||
let key = crate::constants::ATTESTATION_KEY_ID;
|
||||
let attestation_key_exists = syscall!(trussed.exists(Mechanism::P256, key)).exists;
|
||||
if attestation_key_exists {
|
||||
|
||||
// Will panic if certificate does not exist.
|
||||
let cert = syscall!(trussed.read_certificate(
|
||||
crate::constants::ATTESTATION_CERT_ID
|
||||
)).der;
|
||||
let cert =
|
||||
syscall!(trussed.read_certificate(crate::constants::ATTESTATION_CERT_ID)).der;
|
||||
|
||||
let mut aaguid = self.yank_aaguid(cert.as_slice());
|
||||
|
||||
@@ -204,7 +192,6 @@ impl Identity {
|
||||
(None, *b"AAGUID0123456789")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
@@ -220,7 +207,9 @@ pub struct CredentialManagementEnumerateCredentials {
|
||||
pub prev_filename: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, /*uDebug,*/ Default, /*PartialEq,*/ serde::Deserialize, serde::Serialize)]
|
||||
#[derive(
|
||||
Clone, Debug, /*uDebug,*/ Default, /*PartialEq,*/ serde::Deserialize, serde::Serialize,
|
||||
)]
|
||||
pub struct ActiveGetAssertionData {
|
||||
pub rp_id_hash: [u8; 32],
|
||||
pub client_data_hash: [u8; 32],
|
||||
@@ -230,7 +219,9 @@ pub struct ActiveGetAssertionData {
|
||||
pub extensions: Option<ctap_types::ctap2::get_assertion::ExtensionsInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, /*uDebug,*/ Default, /*PartialEq,*/ serde::Deserialize, serde::Serialize)]
|
||||
#[derive(
|
||||
Clone, Debug, /*uDebug,*/ Default, /*PartialEq,*/ serde::Deserialize, serde::Serialize,
|
||||
)]
|
||||
pub struct RuntimeState {
|
||||
key_agreement_key: Option<KeyId>,
|
||||
pin_token: Option<KeyId>,
|
||||
@@ -280,7 +271,6 @@ pub struct PersistentState {
|
||||
}
|
||||
|
||||
impl PersistentState {
|
||||
|
||||
const RESET_RETRIES: u8 = 8;
|
||||
const FILENAME: &'static [u8] = b"persistent-state.cbor";
|
||||
const MAX_RESIDENT_CREDENTIALS_GUESSTIMATE: u32 = 100;
|
||||
@@ -290,12 +280,10 @@ impl PersistentState {
|
||||
}
|
||||
|
||||
pub fn load<T: client::Client + client::Chacha8Poly1305>(trussed: &mut T) -> Result<Self> {
|
||||
|
||||
// TODO: add "exists_file" method instead?
|
||||
let result = try_syscall!(trussed.read_file(
|
||||
Location::Internal,
|
||||
PathBuf::from(Self::FILENAME),
|
||||
)).map_err(|_| Error::Other);
|
||||
let result =
|
||||
try_syscall!(trussed.read_file(Location::Internal, PathBuf::from(Self::FILENAME),))
|
||||
.map_err(|_| Error::Other);
|
||||
|
||||
if result.is_err() {
|
||||
info!("err loading: {:?}", result.err().unwrap());
|
||||
@@ -342,13 +330,16 @@ impl PersistentState {
|
||||
self.save(trussed)
|
||||
}
|
||||
|
||||
pub fn load_if_not_initialised<T: client::Client + client::Chacha8Poly1305>(&mut self, trussed: &mut T) {
|
||||
pub fn load_if_not_initialised<T: client::Client + client::Chacha8Poly1305>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) {
|
||||
if !self.initialised {
|
||||
match Self::load(trussed) {
|
||||
Ok(previous_self) => {
|
||||
info!("loaded previous state!");
|
||||
*self = previous_self
|
||||
},
|
||||
}
|
||||
Err(_err) => {
|
||||
info!("error with previous state! {:?}", _err);
|
||||
}
|
||||
@@ -364,33 +355,47 @@ impl PersistentState {
|
||||
Ok(now)
|
||||
}
|
||||
|
||||
pub fn key_encryption_key<T: client::Client + client::Chacha8Poly1305>(&mut self, trussed: &mut T) -> Result<KeyId>
|
||||
{
|
||||
pub fn key_encryption_key<T: client::Client + client::Chacha8Poly1305>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) -> Result<KeyId> {
|
||||
match self.key_encryption_key {
|
||||
Some(key) => Ok(key),
|
||||
None => self.rotate_key_encryption_key(trussed),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate_key_encryption_key<T: client::Client + client::Chacha8Poly1305>(&mut self, trussed: &mut T) -> Result<KeyId> {
|
||||
if let Some(key) = self.key_encryption_key { syscall!(trussed.delete(key)); }
|
||||
pub fn rotate_key_encryption_key<T: client::Client + client::Chacha8Poly1305>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) -> Result<KeyId> {
|
||||
if let Some(key) = self.key_encryption_key {
|
||||
syscall!(trussed.delete(key));
|
||||
}
|
||||
let key = syscall!(trussed.generate_chacha8poly1305_key(Location::Internal)).key;
|
||||
self.key_encryption_key = Some(key);
|
||||
self.save(trussed)?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
pub fn key_wrapping_key<T: client::Client + client::Chacha8Poly1305>(&mut self, trussed: &mut T) -> Result<KeyId>
|
||||
{
|
||||
pub fn key_wrapping_key<T: client::Client + client::Chacha8Poly1305>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) -> Result<KeyId> {
|
||||
match self.key_wrapping_key {
|
||||
Some(key) => Ok(key),
|
||||
None => self.rotate_key_wrapping_key(trussed),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate_key_wrapping_key<T: client::Client + client::Chacha8Poly1305>(&mut self, trussed: &mut T) -> Result<KeyId> {
|
||||
pub fn rotate_key_wrapping_key<T: client::Client + client::Chacha8Poly1305>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) -> Result<KeyId> {
|
||||
self.load_if_not_initialised(trussed);
|
||||
if let Some(key) = self.key_wrapping_key { syscall!(trussed.delete(key)); }
|
||||
if let Some(key) = self.key_wrapping_key {
|
||||
syscall!(trussed.delete(key));
|
||||
}
|
||||
let key = syscall!(trussed.generate_chacha8poly1305_key(Location::Internal)).key;
|
||||
self.key_wrapping_key = Some(key);
|
||||
self.save(trussed)?;
|
||||
@@ -409,7 +414,7 @@ impl PersistentState {
|
||||
self.consecutive_pin_mismatches >= Self::RESET_RETRIES
|
||||
}
|
||||
|
||||
fn reset_retries<T: TrussedClient>(&mut self, trussed: &mut T) -> Result<()> {
|
||||
fn reset_retries<T: TrussedClient>(&mut self, trussed: &mut T) -> Result<()> {
|
||||
if self.consecutive_pin_mismatches > 0 {
|
||||
self.consecutive_pin_mismatches = 0;
|
||||
self.save(trussed)?;
|
||||
@@ -433,17 +438,18 @@ impl PersistentState {
|
||||
self.pin_hash
|
||||
}
|
||||
|
||||
pub fn set_pin_hash<T: TrussedClient>(&mut self, trussed: &mut T, pin_hash: [u8; 16]) -> Result<()> {
|
||||
pub fn set_pin_hash<T: TrussedClient>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
pin_hash: [u8; 16],
|
||||
) -> Result<()> {
|
||||
self.pin_hash = Some(pin_hash);
|
||||
self.save(trussed)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
impl RuntimeState {
|
||||
|
||||
const POWERCYCLE_RETRIES: u8 = 3;
|
||||
|
||||
fn decrement_retries(&mut self) -> Result<()> {
|
||||
@@ -461,7 +467,6 @@ impl RuntimeState {
|
||||
self.consecutive_pin_mismatches = 0;
|
||||
}
|
||||
|
||||
|
||||
pub fn pin_blocked(&self) -> bool {
|
||||
self.consecutive_pin_mismatches >= Self::POWERCYCLE_RETRIES
|
||||
}
|
||||
@@ -482,13 +487,17 @@ impl RuntimeState {
|
||||
self.cached_credentials.push(credential);
|
||||
}
|
||||
|
||||
pub fn pop_credential<T: client::FilesystemClient>(&mut self, trussed: &mut T) -> Option<Credential> {
|
||||
pub fn pop_credential<T: client::FilesystemClient>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) -> Option<Credential> {
|
||||
let cached_credential = self.cached_credentials.pop()?;
|
||||
|
||||
let credential_data = syscall!(trussed.read_file(
|
||||
Location::Internal,
|
||||
PathBuf::from(cached_credential.path.as_str()),
|
||||
)).data;
|
||||
))
|
||||
.data;
|
||||
|
||||
Credential::deserialize(&credential_data).ok()
|
||||
}
|
||||
@@ -528,13 +537,18 @@ impl RuntimeState {
|
||||
|
||||
pub fn rotate_pin_token<T: client::HmacSha256>(&mut self, trussed: &mut T) -> KeyId {
|
||||
// TODO: need to rotate key agreement key?
|
||||
if let Some(token) = self.pin_token { syscall!(trussed.delete(token)); }
|
||||
if let Some(token) = self.pin_token {
|
||||
syscall!(trussed.delete(token));
|
||||
}
|
||||
let token = syscall!(trussed.generate_secret_key(16, Location::Volatile)).key;
|
||||
self.pin_token = Some(token);
|
||||
token
|
||||
}
|
||||
|
||||
pub fn reset<T: client::HmacSha256 + client::P256 + client::FilesystemClient>(&mut self, trussed: &mut T) {
|
||||
pub fn reset<T: client::HmacSha256 + client::P256 + client::FilesystemClient>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
) {
|
||||
// Could use `free_credential_heap`, but since we're deleting everything here, this is quicker.
|
||||
syscall!(trussed.delete_all(Location::Volatile));
|
||||
self.clear_credential_cache();
|
||||
@@ -542,22 +556,32 @@ impl RuntimeState {
|
||||
|
||||
self.rotate_pin_token(trussed);
|
||||
self.rotate_key_agreement_key(trussed);
|
||||
|
||||
}
|
||||
|
||||
pub fn generate_shared_secret<T: client::P256>(&mut self, trussed: &mut T, platform_key_agreement_key: &CoseEcdhEsHkdf256PublicKey) -> Result<KeyId> {
|
||||
pub fn generate_shared_secret<T: client::P256>(
|
||||
&mut self,
|
||||
trussed: &mut T,
|
||||
platform_key_agreement_key: &CoseEcdhEsHkdf256PublicKey,
|
||||
) -> Result<KeyId> {
|
||||
let private_key = self.key_agreement_key(trussed);
|
||||
|
||||
let serialized_pkak = cbor_serialize_message(platform_key_agreement_key).map_err(|_| Error::InvalidParameter)?;
|
||||
let serialized_pkak = cbor_serialize_message(platform_key_agreement_key)
|
||||
.map_err(|_| Error::InvalidParameter)?;
|
||||
let platform_kak = try_syscall!(trussed.deserialize_p256_key(
|
||||
&serialized_pkak, types::KeySerialization::EcdhEsHkdf256,
|
||||
&serialized_pkak,
|
||||
types::KeySerialization::EcdhEsHkdf256,
|
||||
types::StorageAttributes::new().set_persistence(types::Location::Volatile)
|
||||
)).map_err(|_| Error::InvalidParameter)?.key;
|
||||
))
|
||||
.map_err(|_| Error::InvalidParameter)?
|
||||
.key;
|
||||
|
||||
let pre_shared_secret = syscall!(trussed.agree(
|
||||
types::Mechanism::P256, private_key, platform_kak,
|
||||
types::Mechanism::P256,
|
||||
private_key,
|
||||
platform_kak,
|
||||
types::StorageAttributes::new().set_persistence(types::Location::Volatile),
|
||||
)).shared_secret;
|
||||
))
|
||||
.shared_secret;
|
||||
syscall!(trussed.delete(platform_kak));
|
||||
|
||||
if let Some(previous_shared_secret) = self.shared_secret {
|
||||
@@ -565,13 +589,16 @@ impl RuntimeState {
|
||||
}
|
||||
|
||||
let shared_secret = syscall!(trussed.derive_key(
|
||||
types::Mechanism::Sha256, pre_shared_secret, None, types::StorageAttributes::new().set_persistence(types::Location::Volatile)
|
||||
)).key;
|
||||
types::Mechanism::Sha256,
|
||||
pre_shared_secret,
|
||||
None,
|
||||
types::StorageAttributes::new().set_persistence(types::Location::Volatile)
|
||||
))
|
||||
.key;
|
||||
self.shared_secret = Some(shared_secret);
|
||||
|
||||
syscall!(trussed.delete(pre_shared_secret));
|
||||
|
||||
Ok(shared_secret)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user