Skip to content

Commit

Permalink
Update ctap-types to 0.2.0
Browse files Browse the repository at this point in the history
This mainly brings the following changes:
- Many types are now non-exhaustive, so we need to use Default or
  builders.
- Use byte arrays instead of slices or Bytes<_> for fixed-length byte
  strings.
- Use references for all requests where possible.
- Replace ctap_types::cose with the cosey crate to de-duplicate code.
  • Loading branch information
robin-nitrokey committed Jun 21, 2024
1 parent 07ff03b commit 79b05b5
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 174 deletions.
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ name = "usbip"
required-features = ["dispatch"]

[dependencies]
ctap-types = { version = "0.1.0", features = ["large-blobs"] }
ctap-types = { version = "0.2.0", features = ["large-blobs"] }
cosey = "0.3"
delog = "0.1.0"
heapless = "0.7"
serde = { version = "1.0", default-features = false }
serde_bytes = { version = "0.11.14", default-features = false }
serde-indexed = "0.1.0"
sha2 = { version = "0.10", default-features = false }
trussed = "0.1"
Expand Down Expand Up @@ -66,14 +68,12 @@ usbd-ctaphid = "0.1.0"
features = ["dispatch"]

[patch.crates-io]
ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "4846817d9cd44604121680a19d46f3264973a3ce" }
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/sosthene-nitrokey/littlefs2.git", rev = "2b45a7559ff44260c6dd693e4cb61f54ae5efc53" }
serde-indexed = { git = "https://github.com/sosthene-nitrokey/serde-indexed.git", rev = "5005d23cb4ee8622e62188ea0f9466146f851f0d" }
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" }
trussed-hkdf = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "hkdf-v0.2.0" }
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.3.0" }
trussed-usbip = { git = "https://github.com/Nitrokey/pc-usbip-runner.git", tag = "v0.0.1-nitrokey.1" }
usbd-ctaphid = { git = "https://github.com/Nitrokey/usbd-ctaphid.git", tag = "v0.1.0-nitrokey.2" }
usbd-ctaphid = { git = "https://github.com/trussed-dev/usbd-ctaphid.git", rev = "dcff9009c3cd1ef9e5b09f8f307aca998fc9a8c8" }
65 changes: 37 additions & 28 deletions src/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use core::cmp::Ordering;

use serde::Serialize;
use serde_bytes::ByteArray;
use trussed::{client, syscall, try_syscall, types::KeyId};

pub(crate) use ctap_types::{
Expand Down Expand Up @@ -35,20 +36,19 @@ impl CredentialId {
trussed: &mut T,
credential: &C,
key_encryption_key: KeyId,
rp_id_hash: &Bytes<32>,
nonce: &Bytes<12>,
rp_id_hash: &[u8; 32],
nonce: &[u8; 12],
) -> Result<Self> {
let serialized_credential: SerializedCredential =
trussed::cbor_serialize_bytes(credential).map_err(|_| Error::Other)?;
let message = &serialized_credential;
// info!("serialized cred = {:?}", message).ok();
let associated_data = &rp_id_hash[..];
let nonce: [u8; 12] = nonce.as_slice().try_into().unwrap();
let encrypted_serialized_credential = syscall!(trussed.encrypt_chacha8poly1305(
key_encryption_key,
message,
associated_data,
Some(&nonce)
Some(nonce)
));
EncryptedSerializedCredential(encrypted_serialized_credential)
.try_into()
Expand Down Expand Up @@ -117,15 +117,15 @@ pub enum Credential {
impl Credential {
pub fn try_from<UP: UserPresence, T: client::Client + client::Chacha8Poly1305>(
authnr: &mut Authenticator<UP, T>,
rp_id_hash: &Bytes<32>,
rp_id_hash: &[u8; 32],
descriptor: &PublicKeyCredentialDescriptorRef,
) -> Result<Self> {
Self::try_from_bytes(authnr, rp_id_hash, descriptor.id)
}

pub fn try_from_bytes<UP: UserPresence, T: client::Client + client::Chacha8Poly1305>(
authnr: &mut Authenticator<UP, T>,
rp_id_hash: &Bytes<32>,
rp_id_hash: &[u8; 32],
id: &[u8],
) -> Result<Self> {
let mut cred: Bytes<MAX_CREDENTIAL_ID_LENGTH> = Bytes::new();
Expand Down Expand Up @@ -162,7 +162,7 @@ impl Credential {
&self,
trussed: &mut T,
key_encryption_key: KeyId,
rp_id_hash: &Bytes<32>,
rp_id_hash: &[u8; 32],
) -> Result<CredentialId> {
match self {
Self::Full(credential) => credential.id(trussed, key_encryption_key, Some(rp_id_hash)),
Expand Down Expand Up @@ -238,7 +238,7 @@ pub struct CredentialData {

// extensions (cont. -- we can only append new options due to index-based deserialization)
#[serde(skip_serializing_if = "Option::is_none")]
pub large_blob_key: Option<Bytes<32>>,
pub large_blob_key: Option<ByteArray<32>>,
}

// TODO: figure out sizes
Expand All @@ -248,7 +248,7 @@ pub struct CredentialData {
pub struct FullCredential {
ctap: CtapVersion,
pub data: CredentialData,
nonce: Bytes<12>,
nonce: ByteArray<12>,
}

// Alas... it would be more symmetrical to have Credential { meta, data },
Expand Down Expand Up @@ -331,7 +331,7 @@ impl FullCredential {
timestamp: u32,
hmac_secret: Option<bool>,
cred_protect: Option<CredentialProtectionPolicy>,
large_blob_key: Option<Bytes<32>>,
large_blob_key: Option<ByteArray<32>>,
nonce: [u8; 12],
) -> Self {
info!("credential for algorithm {}", algorithm);
Expand All @@ -354,7 +354,7 @@ impl FullCredential {
FullCredential {
ctap,
data,
nonce: Bytes::from_slice(&nonce).unwrap(),
nonce: ByteArray::new(nonce),
}
}

Expand All @@ -375,14 +375,15 @@ impl FullCredential {
&self,
trussed: &mut T,
key_encryption_key: KeyId,
rp_id_hash: Option<&Bytes<32>>,
rp_id_hash: Option<&[u8; 32]>,
) -> Result<CredentialId> {
let rp_id_hash: Bytes<32> = if let Some(hash) = rp_id_hash {
hash.clone()
let rp_id_hash: [u8; 32] = if let Some(hash) = rp_id_hash {
*hash
} else {
syscall!(trussed.hash_sha256(self.rp.id.as_ref()))
.hash
.to_bytes()
.as_slice()
.try_into()
.map_err(|_| Error::Other)?
};
if self.use_short_id.unwrap_or_default() {
Expand Down Expand Up @@ -446,15 +447,15 @@ pub struct StrippedCredential {
pub use_counter: bool,
pub algorithm: i32,
pub key: Key,
pub nonce: Bytes<12>,
pub nonce: ByteArray<12>,
// extensions
#[serde(skip_serializing_if = "Option::is_none")]
pub hmac_secret: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cred_protect: Option<CredentialProtectionPolicy>,
// TODO: HACK -- remove
#[serde(skip_serializing_if = "Option::is_none")]
pub large_blob_key: Option<Bytes<32>>,
pub large_blob_key: Option<ByteArray<32>>,
}

impl StrippedCredential {
Expand All @@ -472,7 +473,7 @@ impl StrippedCredential {
&self,
trussed: &mut T,
key_encryption_key: KeyId,
rp_id_hash: &Bytes<32>,
rp_id_hash: &[u8; 32],
) -> Result<CredentialId> {
CredentialId::new(trussed, self, key_encryption_key, rp_id_hash, &self.nonce)
}
Expand All @@ -486,10 +487,10 @@ impl From<&FullCredential> for StrippedCredential {
use_counter: credential.data.use_counter,
algorithm: credential.data.algorithm,
key: credential.data.key.clone(),
nonce: credential.nonce.clone(),
nonce: credential.nonce,
hmac_secret: credential.data.hmac_secret,
cred_protect: credential.data.cred_protect,
large_blob_key: credential.data.large_blob_key.clone(),
large_blob_key: credential.data.large_blob_key,
}
}
}
Expand Down Expand Up @@ -523,10 +524,17 @@ mod test {
hmac_secret: Some(false),
cred_protect: None,
use_short_id: Some(true),
large_blob_key: Some(Bytes::from_slice(&[0xff; 32]).unwrap()),
large_blob_key: Some(ByteArray::new([0xff; 32])),
}
}

fn random_byte_array<const N: usize>() -> ByteArray<N> {
use rand::{rngs::OsRng, RngCore};
let mut bytes = [0; N];
OsRng.fill_bytes(&mut bytes);
ByteArray::new(bytes)
}

fn random_bytes<const N: usize>() -> Bytes<N> {
use rand::{
distributions::{Distribution, Uniform},
Expand Down Expand Up @@ -602,7 +610,7 @@ mod test {
hmac_secret: Some(false),
cred_protect: None,
use_short_id: Some(true),
large_blob_key: Some(random_bytes()),
large_blob_key: Some(random_byte_array()),
}
}

Expand All @@ -627,8 +635,7 @@ mod test {
fn credential_ids() {
trussed::virt::with_ram_client("fido", |mut client| {
let kek = syscall!(client.generate_chacha8poly1305_key(Location::Internal)).key;
let mut nonce = Bytes::new();
nonce.extend_from_slice(&[0; 12]).unwrap();
let nonce = ByteArray::new([0; 12]);
let data = credential_data();
let mut full_credential = FullCredential {
ctap: CtapVersion::Fido21Pre,
Expand All @@ -637,7 +644,8 @@ mod test {
};
let rp_id_hash = syscall!(client.hash_sha256(full_credential.rp.id.as_ref()))
.hash
.to_bytes()
.as_slice()
.try_into()
.unwrap();

// Case 1: credential with use_short_id = Some(true) uses new (short) format
Expand Down Expand Up @@ -681,16 +689,17 @@ mod test {
use_counter: true,
algorithm: i32::MAX,
key: Key::WrappedKey(key),
nonce: Bytes::from_slice(&[u8::MAX; 12]).unwrap(),
nonce: ByteArray::new([u8::MAX; 12]),
hmac_secret: Some(true),
cred_protect: Some(CredentialProtectionPolicy::Required),
large_blob_key: Some(Bytes::from_slice(&[0xff; 32]).unwrap()),
large_blob_key: Some(ByteArray::new([0xff; 32])),
};
trussed::virt::with_ram_client("fido", |mut client| {
let kek = syscall!(client.generate_chacha8poly1305_key(Location::Internal)).key;
let rp_id_hash = syscall!(client.hash_sha256(rp_id.as_ref()))
.hash
.to_bytes()
.as_slice()
.try_into()
.unwrap();
let id = credential.id(&mut client, kek, &rp_id_hash).unwrap();
assert_eq!(id.0.len(), 239);
Expand Down
22 changes: 11 additions & 11 deletions src/ctap1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ctap_types::{
ctap1::{authenticate, register, Authenticator, ControlByte, Error, Result},
heapless_bytes::Bytes,
};
use serde_bytes::ByteArray;

use trussed::{
syscall,
Expand Down Expand Up @@ -50,7 +51,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
.serialize_p256_key(public_key, KeySerialization::EcdhEsHkdf256))
.serialized_key;
syscall!(self.trussed.delete(public_key));
let cose_key: ctap_types::cose::EcdhEsHkdf256PublicKey =
let cose_key: cosey::EcdhEsHkdf256PublicKey =
trussed::cbor_deserialize(&serialized_cose_public_key).unwrap();

let wrapping_key = self
Expand All @@ -74,8 +75,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
.to_bytes()
.map_err(|_| Error::UnspecifiedCheckingError)?,
);
let nonce = syscall!(self.trussed.random_bytes(12)).bytes;
let nonce = Bytes::from_slice(&nonce).unwrap();
let nonce = ByteArray::new(self.nonce());

let credential = StrippedCredential {
ctap: credential::CtapVersion::U2fV2,
Expand All @@ -102,14 +102,14 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
.key_encryption_key(&mut self.trussed)
.map_err(|_| Error::NotEnoughMemory)?;
let credential_id = credential
.id(&mut self.trussed, kek, &reg.app_id)
.id(&mut self.trussed, kek, reg.app_id)
.map_err(|_| Error::NotEnoughMemory)?;

let mut commitment = Commitment::new();

commitment.push(0).unwrap(); // reserve byte
commitment.extend_from_slice(&reg.app_id).unwrap();
commitment.extend_from_slice(&reg.challenge).unwrap();
commitment.extend_from_slice(reg.app_id).unwrap();
commitment.extend_from_slice(reg.challenge).unwrap();

commitment.extend_from_slice(&credential_id.0).unwrap();

Expand Down Expand Up @@ -144,14 +144,14 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
Ok(register::Response::new(
0x05,
&cose_key,
&credential_id.0,
credential_id.0,
signature,
&cert,
cert,
))
}

fn authenticate(&mut self, auth: &authenticate::Request) -> Result<authenticate::Response> {
let cred = Credential::try_from_bytes(self, &auth.app_id, &auth.key_handle);
let cred = Credential::try_from_bytes(self, auth.app_id, auth.key_handle);

let user_presence_byte = match auth.control_byte {
ControlByte::CheckOnly => {
Expand Down Expand Up @@ -218,12 +218,12 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti

let mut commitment = Commitment::new();

commitment.extend_from_slice(&auth.app_id).unwrap();
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(&auth.challenge).unwrap();
commitment.extend_from_slice(auth.challenge).unwrap();

let signature = syscall!(self.trussed.sign(
Mechanism::P256,
Expand Down
Loading

0 comments on commit 79b05b5

Please sign in to comment.