Skip to content

Commit

Permalink
Add support for attestation format preferences
Browse files Browse the repository at this point in the history
  • Loading branch information
robin-nitrokey committed Jun 21, 2024
1 parent 9835bbf commit 9e70ef5
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

[Unreleased]: https://github.com/trussed-dev/ctap-types/compare/0.2.0...HEAD

- Remove `Deserialize` implementation for `ctap2::get_assertion::Response`
- Remvoe `Serialize` implementation for `ctap2::{get_assertion, make_credential}::Request`
- Add support for CTAP 2.2
- Support the `thirdPartyPayment` extension
- Add new fields to `get_info`
- Add unsigned extension outputs to `make_credential` and `get_assertion`
- Add enterprise attestation support to `get_assertion`
- Add support for attestation statements in `get_assertion`
- Add support for attestation format preferences

## [0.2.0] - 2024-06-21

Expand Down
10 changes: 8 additions & 2 deletions src/ctap2/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use serde_bytes::ByteArray;
use serde_indexed::{DeserializeIndexed, SerializeIndexed};

use super::make_credential::{AttestationFormatsPreference, AttestationStatement};
use super::{AuthenticatorOptions, Result};
use crate::sizes::*;
use crate::webauthn::*;
Expand Down Expand Up @@ -65,7 +66,7 @@ pub type AuthenticatorData<'a> =

pub type AllowList<'a> = Vec<PublicKeyCredentialDescriptorRef<'a>, MAX_CREDENTIAL_COUNT_IN_LIST>;

#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
#[derive(Clone, Debug, Eq, PartialEq, DeserializeIndexed)]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Request<'a> {
Expand All @@ -83,12 +84,14 @@ pub struct Request<'a> {
pub pin_protocol: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enterprise_attestation: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attestation_formats_preference: Option<AttestationFormatsPreference>,
}

// NB: attn object definition / order at end of
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential
// does not coincide with what python-fido2 expects in AttestationObject.__init__ *at all* :'-)
#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed)]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Response {
Expand All @@ -109,6 +112,8 @@ pub struct Response {
pub unsigned_extension_outputs: Option<UnsignedExtensionOutputs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ep_att: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub att_stmt: Option<AttestationStatement>,
}

#[derive(Debug)]
Expand All @@ -131,6 +136,7 @@ impl ResponseBuilder {
large_blob_key: None,
unsigned_extension_outputs: None,
ep_att: None,
att_stmt: None,
}
}
}
Expand Down
66 changes: 64 additions & 2 deletions src/ctap2/make_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub struct Extensions {
pub third_party_payment: Option<bool>,
}

#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
#[derive(Clone, Debug, Eq, PartialEq, DeserializeIndexed)]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Request<'a> {
Expand All @@ -64,6 +64,8 @@ pub struct Request<'a> {
pub pin_protocol: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enterprise_attestation: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attestation_formats_preference: Option<AttestationFormatsPreference>,
}

pub type AttestationObject = Response;
Expand Down Expand Up @@ -151,14 +153,24 @@ pub enum AttestationStatement {
Packed(PackedAttestationStatement),
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "lowercase")]
pub enum AttestationStatementFormat {
None,
Packed,
}

impl AttestationStatementFormat {
fn from_str(s: &str) -> Option<Self> {
match s {
"none" => Some(Self::None),
"packed" => Some(Self::Packed),
_ => None,
}
}
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct NoneAttestationStatement {}

Expand All @@ -170,6 +182,56 @@ pub struct PackedAttestationStatement {
pub x5c: Option<Vec<Bytes<1024>, 1>>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AttestationFormatsPreference {
known_formats: Vec<AttestationStatementFormat, 2>,
unknown: bool,
}

impl AttestationFormatsPreference {
pub fn known_formats(&self) -> &[AttestationStatementFormat] {
&self.known_formats
}

pub fn includes_unknown_formats(&self) -> bool {
self.unknown
}
}

impl<'de> Deserialize<'de> for AttestationFormatsPreference {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ValueVisitor;

impl<'de> serde::de::Visitor<'de> for ValueVisitor {
type Value = AttestationFormatsPreference;

fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("a sequence")
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut preference = AttestationFormatsPreference::default();
while let Some(value) = seq.next_element::<&str>()? {
if let Some(format) = AttestationStatementFormat::from_str(value) {
preference.known_formats.push(format).ok();
} else {
preference.unknown = true;
}
}
Ok(preference)
}
}

deserializer.deserialize_seq(ValueVisitor)
}
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub struct UnsignedExtensionOutputs {}
Expand Down

0 comments on commit 9e70ef5

Please sign in to comment.