mirror of
https://github.com/trussed-dev/fido-authenticator.git
synced 2026-06-20 04:16:16 -07:00
Support attestation format preference in make_credential
This commit is contained in:
+1
-1
@@ -72,7 +72,7 @@ x509-parser = "0.16.0"
|
||||
features = ["dispatch"]
|
||||
|
||||
[patch.crates-io]
|
||||
ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "ff20dfb5049fb5e25c18d1d27049d0bc98a5be8b" }
|
||||
ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "72eb68b61e3f14957c5ab89bd22f776ac860eb62" }
|
||||
ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" }
|
||||
apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" }
|
||||
littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "2b45a7559ff44260c6dd693e4cb61f54ae5efc53" }
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ doc = false
|
||||
bench = false
|
||||
|
||||
[patch.crates-io]
|
||||
ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "ff20dfb5049fb5e25c18d1d27049d0bc98a5be8b" }
|
||||
ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "72eb68b61e3f14957c5ab89bd22f776ac860eb62" }
|
||||
littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "2b45a7559ff44260c6dd693e4cb61f54ae5efc53" }
|
||||
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "b548d379dcbd67d29453d94847b7bc33ae92e673" }
|
||||
trussed-chunked = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "chunked-v0.1.0" }
|
||||
|
||||
+119
-82
@@ -1,7 +1,11 @@
|
||||
//! The `ctap_types::ctap2::Authenticator` implementation.
|
||||
|
||||
use ctap_types::{
|
||||
ctap2::{self, client_pin::Permissions, Authenticator, VendorOperation},
|
||||
ctap2::{
|
||||
self, client_pin::Permissions, AttestationFormatsPreference, AttestationStatement,
|
||||
AttestationStatementFormat, Authenticator, NoneAttestationStatement,
|
||||
PackedAttestationStatement, VendorOperation,
|
||||
},
|
||||
heapless::{String, Vec},
|
||||
heapless_bytes::Bytes,
|
||||
sizes, ByteArray, Error,
|
||||
@@ -473,102 +477,84 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
|
||||
let serialized_auth_data = authenticator_data.serialize()?;
|
||||
|
||||
// 13.b The Signature
|
||||
let att_stmt_fmt =
|
||||
SupportedAttestationFormat::select(parameters.attestation_formats_preference.as_ref());
|
||||
let att_stmt = if let Some(format) = att_stmt_fmt {
|
||||
match format {
|
||||
SupportedAttestationFormat::None => {
|
||||
Some(AttestationStatement::None(NoneAttestationStatement {}))
|
||||
}
|
||||
SupportedAttestationFormat::Packed => {
|
||||
let mut commitment = Bytes::<1024>::new();
|
||||
commitment
|
||||
.extend_from_slice(&serialized_auth_data)
|
||||
.map_err(|_| Error::Other)?;
|
||||
commitment
|
||||
.extend_from_slice(parameters.client_data_hash)
|
||||
.map_err(|_| Error::Other)?;
|
||||
|
||||
// can we write Sum<M, N> somehow?
|
||||
// debug_now!("seeking commitment, {} + {}", serialized_auth_data.len(), parameters.client_data_hash.len());
|
||||
let mut commitment = Bytes::<1024>::new();
|
||||
commitment
|
||||
.extend_from_slice(&serialized_auth_data)
|
||||
.map_err(|_| Error::Other)?;
|
||||
// debug_now!("serialized_auth_data ={:?}", &serialized_auth_data);
|
||||
commitment
|
||||
.extend_from_slice(parameters.client_data_hash)
|
||||
.map_err(|_| Error::Other)?;
|
||||
// debug_now!("client_data_hash = {:?}", ¶meters.client_data_hash);
|
||||
// debug_now!("commitment = {:?}", &commitment);
|
||||
let (signature, attestation_algorithm) = {
|
||||
if let Some(attestation) = attestation_maybe.as_ref() {
|
||||
let signature = syscall!(self.trussed.sign_p256(
|
||||
attestation.0,
|
||||
&commitment,
|
||||
SignatureSerialization::Asn1Der,
|
||||
))
|
||||
.signature;
|
||||
(signature.to_bytes().map_err(|_| Error::Other)?, -7)
|
||||
} else {
|
||||
match algorithm {
|
||||
SigningAlgorithm::Ed25519 => {
|
||||
let signature =
|
||||
syscall!(self.trussed.sign_ed255(private_key, &commitment))
|
||||
.signature;
|
||||
(signature.to_bytes().map_err(|_| Error::Other)?, -8)
|
||||
}
|
||||
|
||||
// NB: the other/normal one is called "basic" or "batch" attestation,
|
||||
// because it attests the authenticator is part of a batch: the model
|
||||
// specified by AAGUID.
|
||||
// "self signed" is also called "surrogate basic".
|
||||
//
|
||||
// we should also directly support "none" format, it's a bit weird
|
||||
// how browsers firefox this
|
||||
|
||||
let (signature, attestation_algorithm) = {
|
||||
if let Some(attestation) = attestation_maybe.as_ref() {
|
||||
let signature = syscall!(self.trussed.sign_p256(
|
||||
attestation.0,
|
||||
&commitment,
|
||||
SignatureSerialization::Asn1Der,
|
||||
))
|
||||
.signature;
|
||||
(signature.to_bytes().map_err(|_| Error::Other)?, -7)
|
||||
} else {
|
||||
match algorithm {
|
||||
SigningAlgorithm::Ed25519 => {
|
||||
let signature =
|
||||
syscall!(self.trussed.sign_ed255(private_key, &commitment)).signature;
|
||||
(signature.to_bytes().map_err(|_| Error::Other)?, -8)
|
||||
}
|
||||
|
||||
SigningAlgorithm::P256 => {
|
||||
// DO NOT prehash here, `trussed` does that
|
||||
let der_signature = syscall!(self.trussed.sign_p256(
|
||||
private_key,
|
||||
&commitment,
|
||||
SignatureSerialization::Asn1Der
|
||||
))
|
||||
.signature;
|
||||
(der_signature.to_bytes().map_err(|_| Error::Other)?, -7)
|
||||
} // SigningAlgorithm::Totp => {
|
||||
// // maybe we can fake it here too, but seems kinda weird
|
||||
// // return Err(Error::UnsupportedAlgorithm);
|
||||
// // micro-ecc is borked. let's self-sign anyway
|
||||
// let hash = syscall!(self.trussed.hash_sha256(&commitment.as_ref())).hash;
|
||||
// let tmp_key = syscall!(self.trussed
|
||||
// .generate_p256_private_key(Location::Volatile))
|
||||
// .key;
|
||||
|
||||
// let signature = syscall!(self.trussed.sign_p256(
|
||||
// tmp_key,
|
||||
// &hash,
|
||||
// SignatureSerialization::Asn1Der,
|
||||
// )).signature;
|
||||
// (signature.to_bytes().map_err(|_| Error::Other)?, -7)
|
||||
// }
|
||||
SigningAlgorithm::P256 => {
|
||||
// DO NOT prehash here, `trussed` does that
|
||||
let der_signature = syscall!(self.trussed.sign_p256(
|
||||
private_key,
|
||||
&commitment,
|
||||
SignatureSerialization::Asn1Der
|
||||
))
|
||||
.signature;
|
||||
(der_signature.to_bytes().map_err(|_| Error::Other)?, -7)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let packed = PackedAttestationStatement {
|
||||
alg: attestation_algorithm,
|
||||
sig: signature,
|
||||
x5c: attestation_maybe.as_ref().map(|attestation| {
|
||||
// See: https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
|
||||
let cert = attestation.1.clone();
|
||||
let mut x5c = Vec::new();
|
||||
x5c.push(cert).ok();
|
||||
x5c
|
||||
}),
|
||||
};
|
||||
Some(AttestationStatement::Packed(packed))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// debug_now!("SIG = {:?}", &signature);
|
||||
|
||||
if !rk_requested {
|
||||
let _success = syscall!(self.trussed.delete(private_key)).success;
|
||||
info_now!("deleted private credential key: {}", _success);
|
||||
}
|
||||
|
||||
let packed_attn_stmt = ctap2::PackedAttestationStatement {
|
||||
alg: attestation_algorithm,
|
||||
sig: signature,
|
||||
x5c: attestation_maybe.as_ref().map(|attestation| {
|
||||
// See: https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
|
||||
let cert = attestation.1.clone();
|
||||
let mut x5c = Vec::new();
|
||||
x5c.push(cert).ok();
|
||||
x5c
|
||||
}),
|
||||
};
|
||||
|
||||
let fmt = ctap2::AttestationStatementFormat::Packed;
|
||||
let att_stmt = ctap2::AttestationStatement::Packed(packed_attn_stmt);
|
||||
|
||||
let mut attestation_object = ctap2::make_credential::ResponseBuilder {
|
||||
fmt,
|
||||
fmt: att_stmt_fmt
|
||||
.map(From::from)
|
||||
.unwrap_or(AttestationStatementFormat::None),
|
||||
auth_data: serialized_auth_data,
|
||||
}
|
||||
.build();
|
||||
attestation_object.att_stmt = Some(att_stmt);
|
||||
attestation_object.att_stmt = att_stmt;
|
||||
attestation_object.large_blob_key = large_blob_key;
|
||||
Ok(attestation_object)
|
||||
}
|
||||
@@ -1986,6 +1972,57 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum SupportedAttestationFormat {
|
||||
None,
|
||||
Packed,
|
||||
}
|
||||
|
||||
impl SupportedAttestationFormat {
|
||||
fn select(preference: Option<&AttestationFormatsPreference>) -> Option<Self> {
|
||||
let Some(preference) = preference else {
|
||||
// no preference, default to packed format
|
||||
return Some(Self::Packed);
|
||||
};
|
||||
if preference.known_formats() == [AttestationStatementFormat::None]
|
||||
&& !preference.includes_unknown_formats()
|
||||
{
|
||||
// platform requested only None --> omit attestation statement
|
||||
return None;
|
||||
}
|
||||
// use first known and supported format, or default to packed format
|
||||
let format = preference
|
||||
.known_formats()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(Self::try_from)
|
||||
.next()
|
||||
.unwrap_or(Self::Packed);
|
||||
Some(format)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SupportedAttestationFormat> for AttestationStatementFormat {
|
||||
fn from(format: SupportedAttestationFormat) -> Self {
|
||||
match format {
|
||||
SupportedAttestationFormat::None => Self::None,
|
||||
SupportedAttestationFormat::Packed => Self::Packed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AttestationStatementFormat> for SupportedAttestationFormat {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(format: AttestationStatementFormat) -> core::result::Result<Self, Self::Error> {
|
||||
match format {
|
||||
AttestationStatementFormat::None => Ok(Self::None),
|
||||
AttestationStatementFormat::Packed => Ok(Self::Packed),
|
||||
_ => Err(Error::Other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rp_rk_dir(rp_id_hash: &[u8; 32]) -> PathBuf {
|
||||
// uses only first 8 bytes of hash, which should be "good enough"
|
||||
let mut hex = [b'0'; 16];
|
||||
|
||||
Reference in New Issue
Block a user