Support attestation format preference in make_credential

This commit is contained in:
Robin Krahl
2024-06-27 12:50:58 +02:00
parent 30e2b909ad
commit f10cb70941
3 changed files with 121 additions and 84 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 = {:?}", &parameters.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];