Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ disable-reset-time-window = []
# enables support for a large-blob array longer than 1024 bytes
chunked = ["dep:trussed-chunked"]

# enables ML-DSA-44 (FIPS 204, COSE alg -50). When off, the variant, the
# `pubKeyCredParams = -50` arm, the GetInfo algorithm entry, and the
# `Mldsa44` Trussed-core requirement are all elided. Off by default.
# Also bumps `MAX_PACKED_SIG_LENGTH`, x5c element size, and authData buffer
# in ctap-types so packed attestation can carry the 2420-byte ML-DSA-44 sig
# alongside the larger 1322-byte COSE_Key in authData.
mldsa44 = ["trussed-core/mldsa44", "ctap-types/mldsa44"]

log-all = []
log-none = []
log-trace = []
Expand All @@ -52,6 +60,14 @@ cbc = { version = "0.1.2", features = ["alloc"] }
ciborium = "0.2.2"
ciborium-io = "0.2.2"
cipher = "0.4.4"
# The lib's `mldsa44` feature normally turns on both `trussed-core/mldsa44`
# and `ctap-types/mldsa44` together; they keep `Message`'s inner Bytes size
# (1024 → 2048) and `x5c`'s inner Bytes size in lockstep. The dev-dep
# `trussed` below pulls in `trussed-core/mldsa44` unconditionally for the
# test runner, so we need `ctap-types/mldsa44` here too — otherwise the
# `x5c.push(cert)` sites in `ctap2.rs` see `Bytes<2048>` going into a
# `Bytes<1024>` slot and `cargo test` fails to compile.
ctap-types = { version = "0.5", features = ["mldsa44"] }
ctaphid = { version = "0.3.1", default-features = false }
ctaphid-dispatch = "0.4"
delog = { version = "0.1.6", features = ["std-log"] }
Expand All @@ -67,7 +83,7 @@ rand = "0.8.4"
rand_chacha = "0.3"
sha2 = "0.10"
serde_test = "1.0.176"
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b", features = ["virt"] }
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b", features = ["mldsa44", "virt"] }
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.4.0", features = ["chunked", "hkdf", "virt", "fs-info"] }
trussed-usbip = { git = "https://github.com/trussed-dev/pc-usbip-runner.git", rev = "017921df0930707c4af68882ccb1f8b3f1bbf7c5", default-features = false, features = ["ctaphid"] }
usbd-ctaphid = "0.4"
Expand All @@ -77,7 +93,12 @@ x509-parser = "0.16"
features = ["chunked", "dispatch"]

[patch.crates-io]
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b" }
# Track the ctap2.3 branches on the 0x0ece forks of ctap-types + trussed
# until the corresponding PRs (trussed-dev/ctap-types#75,
# trussed-dev/trussed#219) merge upstream.
ctap-types = { git = "https://github.com/0x0ece/ctap-types", rev = "88a5591d9205e54f8e5fae0f3978fc6e20845e79" }
trussed = { git = "https://github.com/0x0ece/trussed", rev = "b69aa1fdf817cc6f679fd695724615f5f91a0812" }
trussed-core = { git = "https://github.com/0x0ece/trussed", rev = "b69aa1fdf817cc6f679fd695724615f5f91a0812" }

[profile.test]
opt-level = 2
7 changes: 6 additions & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,10 @@ doc = false
bench = false

[patch.crates-io]
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b" }
# Track the ctap2.3 branches on the 0x0ece forks of ctap-types + trussed
# until the corresponding PRs (trussed-dev/ctap-types#75,
# trussed-dev/trussed#219) merge upstream.
ctap-types = { git = "https://github.com/0x0ece/ctap-types", rev = "88a5591d9205e54f8e5fae0f3978fc6e20845e79" }
trussed = { git = "https://github.com/0x0ece/trussed", rev = "b69aa1fdf817cc6f679fd695724615f5f91a0812" }
trussed-core = { git = "https://github.com/0x0ece/trussed", rev = "b69aa1fdf817cc6f679fd695724615f5f91a0812" }
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.4.0" }
2 changes: 2 additions & 0 deletions fuzz/fuzz_targets/ctap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ fuzz_target!(|requests: Vec<Request<'_>>| {
max_resident_credential_count: None,
large_blobs: None,
nfc_transport: false,
ccid_transport: false,
firmware_version: 0,
},
);

Expand Down
5 changes: 5 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ pub const ATTESTATION_CERT_ID: CertId = CertId::from_special(0);
pub const ATTESTATION_KEY_ID: KeyId = KeyId::from_special(0);

pub const MAX_RESIDENT_CREDENTIALS_GUESSTIMATE: u32 = 100;

/// Maximum number of bytes of `credBlob` data we accept per credential
/// (CTAP 2.1 §11.1; spec floor is 32). Reported in `authenticatorGetInfo`
/// as `maxCredBlobLength`.
pub const MAX_CRED_BLOB_LENGTH: usize = 32;
28 changes: 28 additions & 0 deletions src/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ impl Credential {
Self::Stripped(credential) => credential.third_party_payment,
}
}

pub fn cred_blob(&self) -> Option<&Bytes<{ crate::constants::MAX_CRED_BLOB_LENGTH }>> {
match self {
Self::Full(credential) => credential.data.cred_blob.as_ref(),
Self::Stripped(credential) => credential.cred_blob.as_ref(),
}
}
}

fn deserialize_bytes<E: serde::de::Error, const N: usize>(
Expand Down Expand Up @@ -518,6 +525,11 @@ pub struct CredentialData {

#[serde(skip_serializing_if = "Option::is_none")]
pub third_party_payment: Option<bool>,

/// `credBlob` extension (CTAP 2.1 §11.1) — platform-supplied bytes
/// associated with this credential, up to `MAX_CRED_BLOB_LENGTH` bytes.
#[serde(skip_serializing_if = "Option::is_none")]
pub cred_blob: Option<Bytes<{ crate::constants::MAX_CRED_BLOB_LENGTH }>>,
}

// TODO: figure out sizes
Expand Down Expand Up @@ -612,6 +624,7 @@ impl FullCredential {
cred_protect: Option<CredentialProtectionPolicy>,
large_blob_key: Option<ByteArray<32>>,
third_party_payment: Option<bool>,
cred_blob: Option<Bytes<{ crate::constants::MAX_CRED_BLOB_LENGTH }>>,
nonce: [u8; 12],
) -> Self {
info!("credential for algorithm {}", algorithm);
Expand All @@ -628,6 +641,7 @@ impl FullCredential {
cred_protect,
large_blob_key,
third_party_payment,
cred_blob,

use_short_id: Some(true),
};
Expand Down Expand Up @@ -735,6 +749,10 @@ pub struct StrippedCredential {
pub large_blob_key: Option<ByteArray<32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub third_party_payment: Option<bool>,
/// Carries the `credBlob` for non-resident credentials inside the
/// credential ID itself — see [`CredentialData::cred_blob`].
#[serde(skip_serializing_if = "Option::is_none")]
pub cred_blob: Option<Bytes<{ crate::constants::MAX_CRED_BLOB_LENGTH }>>,
}

impl StrippedCredential {
Expand Down Expand Up @@ -771,6 +789,7 @@ impl From<&FullCredential> for StrippedCredential {
cred_protect: credential.data.cred_protect,
large_blob_key: credential.data.large_blob_key,
third_party_payment: credential.data.third_party_payment,
cred_blob: credential.data.cred_blob.clone(),
}
}
}
Expand Down Expand Up @@ -814,6 +833,7 @@ mod test {
use_short_id: Some(true),
large_blob_key: Some(ByteArray::new([0xff; 32])),
third_party_payment: Some(true),
cred_blob: None,
}
}

Expand Down Expand Up @@ -845,6 +865,7 @@ mod test {
use_short_id: None,
large_blob_key: None,
third_party_payment: None,
cred_blob: None,
}
}

Expand Down Expand Up @@ -932,6 +953,7 @@ mod test {
use_short_id: Some(true),
large_blob_key: Some(random_byte_array()),
third_party_payment: Some(false),
cred_blob: None,
}
}

Expand Down Expand Up @@ -1092,6 +1114,11 @@ mod test {
cred_protect: Some(CredentialProtectionPolicy::Required),
large_blob_key: Some(ByteArray::new([0xff; 32])),
third_party_payment: Some(true),
// `cred_blob` is intentionally left out of the worst-case fixture:
// adding 32 bytes of blob alongside the other extensions blows past
// `MAX_CREDENTIAL_ID_LENGTH = 255`. Non-RK credentials therefore
// refuse to store `credBlob` (see `make_credential` in `ctap2.rs`).
cred_blob: None,
};
trussed::virt::with_client(StoreConfig::ram(), "fido", |mut client| {
let kek = syscall!(client.generate_chacha8poly1305_key(Location::Internal)).key;
Expand Down Expand Up @@ -1333,6 +1360,7 @@ mod test {
use_short_id: Some(true),
large_blob_key: None,
third_party_payment: None,
cred_blob: None,
},
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/ctap1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
cred_protect: None,
large_blob_key: None,
third_party_payment: None,
cred_blob: None,
};

// info!("made credential {:?}", &credential);
Expand Down Expand Up @@ -139,6 +140,12 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
}
};

// U2F register's `attestation_certificate` is fixed at `Bytes<1024>`.
// Real attestation certs comfortably fit; we lift it from the
// trussed `Message`-typed read so it works regardless of how the
// mldsa44 feature sizes that Message buffer.
let cert =
ctap_types::Bytes::<1024>::try_from(&*cert).map_err(|_| Error::NotEnoughMemory)?;
Ok(register::Response::new(
0x05,
&cose_key,
Expand Down
Loading