ctap2.2: add wire types for FIDO_2_2 and hmac-secret-mc

This commit is contained in:
Emanuele Cesena
2026-05-22 12:43:28 +02:00
committed by Robin Krahl
parent e5bd6cabc9
commit 130c4663ab
4 changed files with 36 additions and 1 deletions
+2
View File
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `AuthenticatorConfig` command.
- Add `credBlob` extension and split `make_credential::Extensions` into `ExtensionsInput` and `ExtensionsOutput`.
- Add `minPinLength` extension.
- Add support for missing CTAP 2.2 features:
- Add `hmac-secret-mc` extension.
## [0.5.0] 2026-03-23
+2
View File
@@ -308,6 +308,7 @@ impl<'a> Arbitrary<'a> for ctap2::make_credential::ExtensionsInput<'a> {
} else {
None
};
let hmac_secret_mc = u.arbitrary()?;
Ok(Self {
cred_protect,
hmac_secret,
@@ -315,6 +316,7 @@ impl<'a> Arbitrary<'a> for ctap2::make_credential::ExtensionsInput<'a> {
#[cfg(feature = "third-party-payment")]
third_party_payment,
cred_blob,
hmac_secret_mc,
})
}
}
+6 -1
View File
@@ -14,7 +14,7 @@ pub struct Response {
// 0x02
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<Vec<Extension, 6>>,
pub extensions: Option<Vec<Extension, 7>>,
// 0x03
pub aaguid: Bytes<16>,
@@ -252,6 +252,7 @@ pub enum Extension {
CredProtect,
CredBlob,
HmacSecret,
HmacSecretMc,
LargeBlobKey,
MinPinLength,
ThirdPartyPayment,
@@ -261,6 +262,7 @@ impl Extension {
const CRED_PROTECT: &'static str = "credProtect";
const CRED_BLOB: &'static str = "credBlob";
const HMAC_SECRET: &'static str = "hmac-secret";
const HMAC_SECRET_MC: &'static str = "hmac-secret-mc";
const LARGE_BLOB_KEY: &'static str = "largeBlobKey";
const MIN_PIN_LENGTH: &'static str = "minPinLength";
const THIRD_PARTY_PAYMENT: &'static str = "thirdPartyPayment";
@@ -272,6 +274,7 @@ impl From<Extension> for &str {
Extension::CredProtect => Extension::CRED_PROTECT,
Extension::CredBlob => Extension::CRED_BLOB,
Extension::HmacSecret => Extension::HMAC_SECRET,
Extension::HmacSecretMc => Extension::HMAC_SECRET_MC,
Extension::LargeBlobKey => Extension::LARGE_BLOB_KEY,
Extension::MinPinLength => Extension::MIN_PIN_LENGTH,
Extension::ThirdPartyPayment => Extension::THIRD_PARTY_PAYMENT,
@@ -287,6 +290,7 @@ impl TryFrom<&str> for Extension {
Self::CRED_PROTECT => Ok(Self::CredProtect),
Self::CRED_BLOB => Ok(Self::CredBlob),
Self::HMAC_SECRET => Ok(Self::HmacSecret),
Self::HMAC_SECRET_MC => Ok(Self::HmacSecretMc),
Self::LARGE_BLOB_KEY => Ok(Self::LargeBlobKey),
Self::MIN_PIN_LENGTH => Ok(Self::MinPinLength),
Self::THIRD_PARTY_PAYMENT => Ok(Self::ThirdPartyPayment),
@@ -475,6 +479,7 @@ mod tests {
(Extension::CredProtect, "credProtect"),
(Extension::CredBlob, "credBlob"),
(Extension::HmacSecret, "hmac-secret"),
(Extension::HmacSecretMc, "hmac-secret-mc"),
(Extension::LargeBlobKey, "largeBlobKey"),
(Extension::MinPinLength, "minPinLength"),
(Extension::ThirdPartyPayment, "thirdPartyPayment"),
+26
View File
@@ -51,6 +51,13 @@ pub struct ExtensionsInput<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub large_blob_key: Option<bool>,
/// `hmac-secret-mc` (CTAP 2.2 §11.4.5 / WebAuthn L3): platform-supplied
/// hmac-secret request evaluated at MakeCredential time, returning
/// hmac-secret outputs alongside the freshly-minted credential.
#[serde(rename = "hmac-secret-mc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub hmac_secret_mc: Option<super::get_assertion::HmacSecretInput>,
#[cfg(feature = "third-party-payment")]
#[serde(rename = "thirdPartyPayment")]
#[serde(skip_serializing_if = "Option::is_none")]
@@ -77,6 +84,13 @@ pub struct ExtensionsOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub hmac_secret: Option<bool>,
/// `hmac-secret-mc` (CTAP 2.2): encrypted hmac-secret outputs produced at
/// MakeCredential time. Wire format mirrors GetAssertion's `hmac-secret`
/// output — `enc(output1)` or `enc(output1 || output2)`, up to 80 bytes.
#[serde(rename = "hmac-secret-mc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub hmac_secret_mc: Option<crate::Bytes<80>>,
#[cfg(feature = "third-party-payment")]
#[serde(rename = "thirdPartyPayment")]
#[serde(skip_serializing_if = "Option::is_none")]
@@ -190,6 +204,8 @@ pub struct UnsignedExtensionOutputs {}
#[cfg(test)]
mod tests {
use super::*;
use crate::ctap2::get_assertion::HmacSecretInput;
use cosey::EcdhEsHkdf256PublicKey;
use serde_test::{assert_ser_tokens, Token};
#[test]
@@ -223,6 +239,15 @@ mod tests {
#[cfg(feature = "third-party-payment")]
third_party_payment: Some(true),
cred_blob: Some(serde_bytes::Bytes::new(b"1234")),
hmac_secret_mc: Some(HmacSecretInput {
key_agreement: EcdhEsHkdf256PublicKey {
x: [0xff; 32].try_into().unwrap(),
y: [0xff; 32].try_into().unwrap(),
},
salt_enc: [0xff; 80].try_into().unwrap(),
salt_auth: [0xff; 32].try_into().unwrap(),
pin_protocol: Some(1),
}),
};
crate::test::assert_canonical_cbor(&extensions);
}
@@ -235,6 +260,7 @@ mod tests {
#[cfg(feature = "third-party-payment")]
third_party_payment: Some(true),
cred_blob: Some(true),
hmac_secret_mc: Some([0xff; 80].try_into().unwrap()),
};
crate::test::assert_canonical_cbor(&extensions);
}