Skip to content

Commit 08bc405

Browse files
author
Martin Sirringhaus
committed
Review CBOR-serializations in attestation.rs
1 parent c74e984 commit 08bc405

File tree

2 files changed

+219
-94
lines changed

2 files changed

+219
-94
lines changed

src/ctap2/attestation.rs

+75-35
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ pub struct Extension {
8080
pub hmac_secret: Option<HmacSecretResponse>,
8181
}
8282

83+
impl Extension {
84+
fn has_some(&self) -> bool {
85+
self.pin_min_length.is_some() || self.hmac_secret.is_some()
86+
}
87+
}
88+
8389
fn parse_extensions(input: &[u8]) -> IResult<&[u8], Extension, NomError<&[u8]>> {
8490
serde_to_nom(input)
8591
}
@@ -284,23 +290,47 @@ impl<'de> Deserialize<'de> for AuthenticatorData {
284290
}
285291

286292
impl AuthenticatorData {
293+
// see https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data
294+
// Authenticator Data
295+
// Name Length (in bytes)
296+
// rpIdHash 32
297+
// flags 1
298+
// signCount 4
299+
// attestedCredentialData variable (if present)
300+
// extensions variable (if present)
287301
pub fn to_vec(&self) -> Result<Vec<u8>, AuthenticatorError> {
288302
let mut data = Vec::new();
289-
data.extend(self.rp_id_hash.0);
290-
data.extend([self.flags.bits()]);
291-
data.extend(self.counter.to_be_bytes());
303+
data.extend(self.rp_id_hash.0); // (1) "rpIDHash", len=32
304+
data.extend([self.flags.bits()]); // (2) "flags", len=1 (u8)
305+
data.extend(self.counter.to_be_bytes()); // (3) "signCount", len=4, 32-bit unsigned big-endian integer.
292306

293-
// TODO(baloo): need to yield credential_data and extensions, but that dependends on flags,
294-
// should we consider another type system?
307+
// TODO(MS): Here flags=AT needs to be set, but this data comes from the security device
308+
// and we (probably?) need to just trust the device to set the right flags
295309
if let Some(cred) = &self.credential_data {
296-
data.extend(cred.aaguid.0);
297-
data.extend((cred.credential_id.len() as u16).to_be_bytes());
298-
data.extend(&cred.credential_id);
310+
// see https://www.w3.org/TR/webauthn-2/#sctn-attested-credential-data
311+
// Attested Credential Data
312+
// Name Length (in bytes)
313+
// aaguid 16
314+
// credentialIdLength 2
315+
// credentialId L
316+
// credentialPublicKey variable
317+
data.extend(cred.aaguid.0); // (1) "aaguid", len=16
318+
data.extend((cred.credential_id.len() as u16).to_be_bytes()); // (2) "credentialIdLength", len=2, 16-bit unsigned big-endian integer
319+
data.extend(&cred.credential_id); // (3) "credentialId", len= see (2)
299320
data.extend(
321+
// (4) "credentialPublicKey", len=variable
300322
&serde_cbor::to_vec(&cred.credential_public_key)
301323
.map_err(CommandError::Serializing)?,
302324
);
303325
}
326+
// TODO(MS): Here flags=ED needs to be set, but this data comes from the security device
327+
// and we (probably?) need to just trust the device to set the right flags
328+
if self.extensions.has_some() {
329+
data.extend(
330+
// (5) "extensions", len=variable
331+
&serde_cbor::to_vec(&self.extensions).map_err(CommandError::Serializing)?,
332+
);
333+
}
304334
Ok(data)
305335
}
306336
}
@@ -382,35 +412,44 @@ pub enum AttestationStatement {
382412
// }
383413

384414
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
385-
// See https://www.w3.org/TR/webauthn/#fido-u2f-attestation
415+
// See https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
416+
// u2fStmtFormat = {
417+
// x5c: [ attestnCert: bytes ],
418+
// sig: bytes
419+
// }
386420
pub struct AttestationStatementFidoU2F {
387-
pub sig: Signature,
388-
389-
#[serde(rename = "x5c")]
390421
/// Certificate chain in x509 format
391-
pub attestation_cert: Vec<AttestationCertificate>,
422+
#[serde(rename = "x5c")]
423+
pub attestation_cert: Vec<AttestationCertificate>, // (1) "x5c"
424+
pub sig: Signature, // (2) "sig"
392425
}
393426

394-
#[allow(dead_code)] // TODO(MS): Remove me, once we can parse AttestationStatements and use this function
395427
impl AttestationStatementFidoU2F {
396428
pub fn new(cert: &[u8], signature: &[u8]) -> Self {
397429
AttestationStatementFidoU2F {
398-
sig: Signature(ByteBuf::from(signature)),
399430
attestation_cert: vec![AttestationCertificate(Vec::from(cert))],
431+
sig: Signature(ByteBuf::from(signature)),
400432
}
401433
}
402434
}
403435

404436
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
405-
// TODO(baloo): there is a couple other options than x5c:
406-
// https://www.w3.org/TR/webauthn/#packed-attestation
437+
// https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation
438+
// packedStmtFormat = {
439+
// alg: COSEAlgorithmIdentifier,
440+
// sig: bytes,
441+
// x5c: [ attestnCert: bytes, * (caCert: bytes) ]
442+
// } //
443+
// {
444+
// alg: COSEAlgorithmIdentifier
445+
// sig: bytes,
446+
// }
407447
pub struct AttestationStatementPacked {
408-
pub alg: COSEAlgorithm,
409-
pub sig: Signature,
410-
411-
#[serde(rename = "x5c")]
448+
pub alg: COSEAlgorithm, // (1) "alg"
449+
pub sig: Signature, // (2) "sig"
412450
/// Certificate chain in x509 format
413-
pub attestation_cert: Vec<AttestationCertificate>,
451+
#[serde(rename = "x5c", skip_serializing_if = "Vec::is_empty", default)]
452+
pub attestation_cert: Vec<AttestationCertificate>, // (3) "x5c"
414453
}
415454

416455
#[derive(Debug, Deserialize, PartialEq, Eq)]
@@ -523,31 +562,32 @@ impl Serialize for AttestationObject {
523562
let map_len = 3;
524563
let mut map = serializer.serialize_map(Some(map_len))?;
525564

526-
let auth_data = self
527-
.auth_data
528-
.to_vec()
529-
.map(serde_cbor::Value::Bytes)
530-
.map_err(|_| SerError::custom("Failed to serialize auth_data"))?;
531-
532565
// CTAP2 canonical CBOR order for these entries is ("fmt", "attStmt", "authData")
533566
// as strings are sorted by length and then lexically.
567+
// see https://www.w3.org/TR/webauthn-2/#attestation-object
534568
match self.att_statement {
535569
AttestationStatement::None => {
536570
// Even with Att None, an empty map is returned in the cbor!
537-
map.serialize_entry(&"fmt", &"none")?;
571+
map.serialize_entry(&"fmt", &"none")?; // (1) "fmt"
538572
let v = serde_cbor::Value::Map(std::collections::BTreeMap::new());
539-
map.serialize_entry(&"attStmt", &v)?;
573+
map.serialize_entry(&"attStmt", &v)?; // (2) "attStmt"
540574
}
541575
AttestationStatement::Packed(ref v) => {
542-
map.serialize_entry(&"fmt", &"packed")?;
543-
map.serialize_entry(&"attStmt", v)?;
576+
map.serialize_entry(&"fmt", &"packed")?; // (1) "fmt"
577+
map.serialize_entry(&"attStmt", v)?; // (2) "attStmt"
544578
}
545579
AttestationStatement::FidoU2F(ref v) => {
546-
map.serialize_entry(&"fmt", &"fido-u2f")?;
547-
map.serialize_entry(&"attStmt", v)?;
580+
map.serialize_entry(&"fmt", &"fido-u2f")?; // (1) "fmt"
581+
map.serialize_entry(&"attStmt", v)?; // (2) "attStmt"
548582
}
549583
}
550-
map.serialize_entry(&"authData", &auth_data)?;
584+
585+
let auth_data = self
586+
.auth_data
587+
.to_vec()
588+
.map(serde_cbor::Value::Bytes)
589+
.map_err(|_| SerError::custom("Failed to serialize auth_data"))?;
590+
map.serialize_entry(&"authData", &auth_data)?; // (3) "authData"
551591
map.end()
552592
}
553593
}

0 commit comments

Comments
 (0)