Revision control
Copy as Markdown
Other Tools
use super::utils::{from_slice_stream, read_be_u16, read_be_u32, read_byte};
use crate::crypto::{COSEAlgorithm, CryptoError, SharedSecret};
use crate::ctap2::server::{CredentialProtectionPolicy, HMACGetSecretOutput, RpIdHash};
use crate::ctap2::utils::serde_parse_err;
use crate::{crypto::COSEKey, errors::AuthenticatorError};
use base64::Engine;
use serde::ser::{Error as SerError, SerializeMap, Serializer};
use serde::{
de::{Error as SerdeError, Unexpected, Visitor},
Deserialize, Deserializer, Serialize,
};
use serde_cbor;
use std::convert::TryInto;
use std::fmt;
use std::io::{Cursor, Read};
#[derive(Debug, PartialEq, Eq)]
pub enum HmacSecretResponse {
/// This is returned by MakeCredential calls to display if CredRandom was
/// successfully generated
Confirmed(bool),
/// This is returned by GetAssertion:
/// AES256-CBC(shared_secret, HMAC-SHA265(CredRandom, salt1) || HMAC-SHA265(CredRandom, salt2))
Secret(Vec<u8>),
}
impl HmacSecretResponse {
/// Return the decrypted HMAC outputs, if this is an instance of [HmacSecretResponse::Secret].
pub fn decrypt_secrets(
&self,
shared_secret: &SharedSecret,
) -> Option<Result<HMACGetSecretOutput, CryptoError>> {
if let HmacSecretResponse::Secret(hmac_outputs) = self {
Some(Self::decrypt_secrets_internal(shared_secret, hmac_outputs))
} else {
None
}
}
fn decrypt_secrets_internal(
shared_secret: &SharedSecret,
hmac_outputs: &[u8],
) -> Result<HMACGetSecretOutput, CryptoError> {
let output_secrets = shared_secret.decrypt(hmac_outputs)?;
match if output_secrets.len() < 32 {
Err(CryptoError::WrongSaltLength)
} else {
let (output1, output2) = output_secrets.split_at(32);
Ok(HMACGetSecretOutput {
output1: output1
.try_into()
.map_err(|_| CryptoError::WrongSaltLength)?,
output2: (!output2.is_empty())
.then(|| output2.try_into().map_err(|_| CryptoError::WrongSaltLength))
.transpose()?,
})
} {
err @ Err(CryptoError::WrongSaltLength) => {
// TODO: Use Result::inspect_err when stable
debug!(
"Bad hmac-secret output length: {} bytes (expected exactly 32 or 64)",
output_secrets.len()
);
err
}
other => other,
}
}
}
impl Serialize for HmacSecretResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
HmacSecretResponse::Confirmed(x) => serializer.serialize_bool(*x),
HmacSecretResponse::Secret(x) => serializer.serialize_bytes(x),
}
}
}
impl<'de> Deserialize<'de> for HmacSecretResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct HmacSecretResponseVisitor;
impl<'de> Visitor<'de> for HmacSecretResponseVisitor {
type Value = HmacSecretResponse;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array or a boolean")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
Ok(HmacSecretResponse::Secret(v.to_vec()))
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: SerdeError,
{
Ok(HmacSecretResponse::Confirmed(v))
}
}
deserializer.deserialize_any(HmacSecretResponseVisitor)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Extension {
#[serde(rename = "credProtect", skip_serializing_if = "Option::is_none")]
pub cred_protect: Option<CredentialProtectionPolicy>,
#[serde(rename = "hmac-secret", skip_serializing_if = "Option::is_none")]
pub hmac_secret: Option<HmacSecretResponse>,
#[serde(rename = "minPinLength", skip_serializing_if = "Option::is_none")]
pub min_pin_length: Option<u64>,
}
impl Extension {
pub fn has_some(&self) -> bool {
self.min_pin_length.is_some() || self.hmac_secret.is_some() || self.cred_protect.is_some()
}
}
#[derive(Serialize, PartialEq, Default, Eq, Clone)]
pub struct AAGuid(pub [u8; 16]);
impl AAGuid {
pub fn from(src: &[u8]) -> Result<AAGuid, AuthenticatorError> {
let mut payload = [0u8; 16];
if src.len() != payload.len() {
Err(AuthenticatorError::InternalError(String::from(
"Failed to parse AAGuid",
)))
} else {
payload.copy_from_slice(src);
Ok(AAGuid(payload))
}
}
}
impl fmt::Debug for AAGuid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"AAGuid({:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x})",
self.0[0],
self.0[1],
self.0[2],
self.0[3],
self.0[4],
self.0[5],
self.0[6],
self.0[7],
self.0[8],
self.0[9],
self.0[10],
self.0[11],
self.0[12],
self.0[13],
self.0[14],
self.0[15]
)
}
}
impl<'de> Deserialize<'de> for AAGuid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct AAGuidVisitor;
impl<'de> Visitor<'de> for AAGuidVisitor {
type Value = AAGuid;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
let mut buf = [0u8; 16];
if v.len() != buf.len() {
return Err(E::invalid_length(v.len(), &"16"));
}
buf.copy_from_slice(v);
Ok(AAGuid(buf))
}
}
deserializer.deserialize_bytes(AAGuidVisitor)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct AttestedCredentialData {
pub aaguid: AAGuid,
pub credential_id: Vec<u8>,
pub credential_public_key: COSEKey,
}
fn parse_attested_cred_data<R: Read, E: SerdeError>(
data: &mut R,
) -> Result<AttestedCredentialData, E> {
let mut aaguid_raw = [0u8; 16];
data.read_exact(&mut aaguid_raw)
.map_err(|_| serde_parse_err("AAGuid"))?;
let aaguid = AAGuid(aaguid_raw);
let cred_len = read_be_u16(data)?;
let mut credential_id = vec![0u8; cred_len as usize];
data.read_exact(&mut credential_id)
.map_err(|_| serde_parse_err("CredentialId"))?;
let credential_public_key = from_slice_stream(data)?;
Ok(AttestedCredentialData {
aaguid,
credential_id,
credential_public_key,
})
}
bitflags! {
// Defining an exhaustive list of flags here ensures that `from_bits_truncate` is lossless and
// that `from_bits` never returns None.
pub struct AuthenticatorDataFlags: u8 {
const USER_PRESENT = 0x01;
const RESERVED_1 = 0x02;
const USER_VERIFIED = 0x04;
const RESERVED_3 = 0x08;
const RESERVED_4 = 0x10;
const RESERVED_5 = 0x20;
const ATTESTED = 0x40;
const EXTENSION_DATA = 0x80;
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct AuthenticatorData {
pub rp_id_hash: RpIdHash,
pub flags: AuthenticatorDataFlags,
pub counter: u32,
pub credential_data: Option<AttestedCredentialData>,
pub extensions: Extension,
}
impl AuthenticatorData {
pub fn to_vec(&self) -> Vec<u8> {
match serde_cbor::value::to_value(self) {
Ok(serde_cbor::value::Value::Bytes(out)) => out,
_ => unreachable!(), // Serialize is guaranteed to produce bytes
}
}
}
impl<'de> Deserialize<'de> for AuthenticatorData {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct AuthenticatorDataVisitor;
impl<'de> Visitor<'de> for AuthenticatorDataVisitor {
type Value = AuthenticatorData;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array")
}
fn visit_bytes<E>(self, input: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
let mut cursor = Cursor::new(input);
let mut rp_id_hash_raw = [0u8; 32];
cursor
.read_exact(&mut rp_id_hash_raw)
.map_err(|_| serde_parse_err("32 bytes"))?;
let rp_id_hash = RpIdHash(rp_id_hash_raw);
// preserve the flags, even if some reserved values are set.
let flags = AuthenticatorDataFlags::from_bits_truncate(read_byte(&mut cursor)?);
let counter = read_be_u32(&mut cursor)?;
let mut credential_data = None;
if flags.contains(AuthenticatorDataFlags::ATTESTED) {
credential_data = Some(parse_attested_cred_data(&mut cursor)?);
}
let extensions = if flags.contains(AuthenticatorDataFlags::EXTENSION_DATA) {
from_slice_stream(&mut cursor)?
} else {
Default::default()
};
// TODO(baloo): we should check for end of buffer and raise a parse
// parse error if data is still in the buffer
Ok(AuthenticatorData {
rp_id_hash,
flags,
counter,
credential_data,
extensions,
})
}
}
deserializer.deserialize_bytes(AuthenticatorDataVisitor)
}
}
impl Serialize for AuthenticatorData {
// Authenticator Data
// Name Length (in bytes)
// rpIdHash 32
// flags 1
// signCount 4
// attestedCredentialData variable (if present)
// extensions variable (if present)
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut data = Vec::new();
data.extend(self.rp_id_hash.0); // (1) "rpIDHash", len=32
data.extend([self.flags.bits()]); // (2) "flags", len=1 (u8)
data.extend(self.counter.to_be_bytes()); // (3) "signCount", len=4, 32-bit unsigned big-endian integer.
if let Some(cred) = &self.credential_data {
// Attested Credential Data
// Name Length (in bytes)
// aaguid 16
// credentialIdLength 2
// credentialId L
// credentialPublicKey variable
data.extend(cred.aaguid.0); // (1) "aaguid", len=16
data.extend((cred.credential_id.len() as u16).to_be_bytes()); // (2) "credentialIdLength", len=2, 16-bit unsigned big-endian integer
data.extend(&cred.credential_id); // (3) "credentialId", len= see (2)
data.extend(
// (4) "credentialPublicKey", len=variable
&serde_cbor::to_vec(&cred.credential_public_key)
.map_err(|_| SerError::custom("Failed to serialize auth_data"))?,
);
}
// If we have parsed extension data, then we should serialize it even if the authenticator
// failed to set the extension data flag.
// If we don't have parsed extension data, then what we output depends on the flag.
// If the flag is set, we output the empty CBOR map. If it is not set, we output nothing.
if self.extensions.has_some() || self.flags.contains(AuthenticatorDataFlags::EXTENSION_DATA)
{
data.extend(
// (5) "extensions", len=variable
&serde_cbor::to_vec(&self.extensions)
.map_err(|_| SerError::custom("Failed to serialize auth_data"))?,
);
}
serializer.serialize_bytes(&data)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
/// x509 encoded attestation certificate
pub struct AttestationCertificate(#[serde(with = "serde_bytes")] pub Vec<u8>);
impl AsRef<[u8]> for AttestationCertificate {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq)]
pub struct Signature(#[serde(with = "serde_bytes")] pub Vec<u8>);
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&self.0);
write!(f, "Signature({value})")
}
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl From<&[u8]> for Signature {
fn from(sig: &[u8]) -> Signature {
Signature(sig.to_vec())
}
}
#[derive(Debug, PartialEq, Eq, Deserialize)]
// The tag and content attributes here are really for AttestationObject, which contains an
// "internally tagged" AttestationStatement.
#[serde(tag = "fmt", content = "attStmt", rename_all = "lowercase")]
pub enum AttestationStatement {
#[serde(deserialize_with = "deserialize_none_att_stmt")]
None,
Packed(AttestationStatementPacked),
#[serde(rename = "fido-u2f")]
FidoU2F(AttestationStatementFidoU2F),
// The remaining attestation statement formats are deserialized as serde_cbor::Values---we do
// not perform any validation of their contents. These are expected to be used primarily when
// anonymizing attestation objects that contain attestation statements in these formats.
#[serde(rename = "android-key")]
AndroidKey(serde_cbor::Value),
#[serde(rename = "android-safetynet")]
AndroidSafetyNet(serde_cbor::Value),
Apple(serde_cbor::Value),
Tpm(serde_cbor::Value),
}
impl AttestationStatement {
/// The [attestation statement format identifier][att-fmt-id].
///
pub fn id(&self) -> &str {
match self {
Self::None => "none",
Self::Packed(..) => "packed",
Self::FidoU2F(..) => "fido-u2f",
Self::AndroidKey(..) => "android-key",
Self::AndroidSafetyNet(..) => "android-safetynet",
Self::Apple(..) => "apple",
Self::Tpm(..) => "tpm",
}
}
}
impl Serialize for AttestationStatement {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::None => serializer.serialize_map(Some(0))?.end(),
Self::Packed(ref v) => serializer.serialize_some(v),
Self::FidoU2F(ref v) => serializer.serialize_some(v),
Self::AndroidKey(ref v) => serializer.serialize_some(v),
Self::AndroidSafetyNet(ref v) => serializer.serialize_some(v),
Self::Apple(ref v) => serializer.serialize_some(v),
Self::Tpm(ref v) => serializer.serialize_some(v),
}
}
}
// AttestationStatement::None is serialized as the empty map. We need to enforce
// the emptyness condition manually while deserializing.
fn deserialize_none_att_stmt<'de, D>(deserializer: D) -> Result<(), D::Error>
where
D: Deserializer<'de>,
{
let map = <std::collections::BTreeMap<(), ()>>::deserialize(deserializer)?;
if !map.is_empty() {
return Err(D::Error::invalid_value(Unexpected::Map, &"the empty map"));
}
Ok(())
}
// Not all crypto-backends currently provide "crypto::verify()", so we do not implement it yet.
// Also not sure, if we really need it. Would be a sanity-check only, to verify the signature is valid,
// before sendig it out.
// impl AttestationStatement {
// pub fn verify(&self, data: &[u8]) -> Result<bool, AuthenticatorError> {
// match self {
// AttestationStatement::None => Ok(true),
// AttestationStatement::Unparsed(_) => Err(AuthenticatorError::Custom(
// "Unparsed attestation object can't be used to verify signature.".to_string(),
// )),
// AttestationStatement::FidoU2F(att) => {
// let res = crypto::verify(
// crypto::SignatureAlgorithm::ES256,
// &att.attestation_cert[0].as_ref(),
// att.sig.as_ref(),
// data,
// )?;
// Ok(res)
// }
// AttestationStatement::Packed(att) => {
// if att.alg != Alg::ES256 {
// return Err(AuthenticatorError::Custom(
// "Verification only supported for ES256".to_string(),
// ));
// }
// let res = crypto::verify(
// crypto::SignatureAlgorithm::ES256,
// att.attestation_cert[0].as_ref(),
// att.sig.as_ref(),
// data,
// )?;
// Ok(res)
// }
// }
// }
// }
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
// u2fStmtFormat = {
// x5c: [ attestnCert: bytes ],
// sig: bytes
// }
pub struct AttestationStatementFidoU2F {
pub sig: Signature, // (1) "sig"
/// Certificate chain in x509 format
#[serde(rename = "x5c")]
pub attestation_cert: Vec<AttestationCertificate>, // (2) "x5c"
}
impl AttestationStatementFidoU2F {
pub fn new(cert: &[u8], signature: &[u8]) -> Self {
AttestationStatementFidoU2F {
attestation_cert: vec![AttestationCertificate(Vec::from(cert))],
sig: Signature::from(signature),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
// packedStmtFormat = {
// alg: COSEAlgorithmIdentifier,
// sig: bytes,
// x5c: [ attestnCert: bytes, * (caCert: bytes) ]
// } //
// {
// alg: COSEAlgorithmIdentifier
// sig: bytes,
// }
pub struct AttestationStatementPacked {
pub alg: COSEAlgorithm, // (1) "alg"
pub sig: Signature, // (2) "sig"
/// Certificate chain in x509 format
#[serde(rename = "x5c", skip_serializing_if = "Vec::is_empty", default)]
pub attestation_cert: Vec<AttestationCertificate>, // (3) "x5c"
}
// A WebAuthn attestation object is a CBOR map with keys "fmt", "attStmt", and "authData". The
// "fmt" field determines the type of "attStmt". The flatten attribute here turns the tag and
// content attributes on AttestationStatement (defined above) into expected keys for
// AttestationObject, which allows us to derive Deserialize. Like many of our other structs, the
// derived Deserialize implementation is permissive: it does not enforce CTAP2 canonical CBOR
// encoding and it allows repeated keys (the last one wins).
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AttestationObject {
pub auth_data: AuthenticatorData,
#[serde(flatten)]
pub att_stmt: AttestationStatement,
}
impl AttestationObject {
pub fn anonymize(&mut self) {
// Remove the attestation statement and the AAGUID from the authenticator data.
self.att_stmt = AttestationStatement::None;
if let Some(credential_data) = self.auth_data.credential_data.as_mut() {
credential_data.aaguid = AAGuid::default();
}
}
}
impl Serialize for AttestationObject {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_map!(
serializer,
// CTAP2 canonical CBOR order for these entries is ("fmt", "attStmt", "authData")
// as strings are sorted by length and then lexically.
&"fmt" => self.att_stmt.id(),
&"attStmt" => &self.att_stmt,
&"authData" => &self.auth_data,
)
}
}
#[cfg(test)]
pub mod test {
use super::super::utils::from_slice_stream;
use super::*;
use crate::crypto::{COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, Curve};
use serde_cbor::{from_slice, to_vec};
const SAMPLE_ATTESTATION_STMT_NONE: [u8; 19] = [
0xa2, // map(2)
0x63, // text(3)
0x66, 0x6d, 0x74, // "fmt"
0x64, // text(4)
0x6e, 0x6f, 0x6e, 0x65, // "none"
0x67, // text(7)
0x61, 0x74, 0x74, 0x53, 0x74, 0x6d, 0x74, // "attStmt"
0xa0, // map(0)
];
const SAMPLE_ATTESTATION_STMT_FIDO_U2F: [u8; 840] = [
0xa2, // map(2)
0x63, // text(3)
0x66, 0x6d, 0x74, // "fmt"
0x68, // text(8)
0x66, 0x69, 0x64, 0x6f, 0x2d, 0x75, 0x32, 0x66, // "fido-u2f"
0x67, // text(7)
0x61, 0x74, 0x74, 0x53, 0x74, 0x6d, 0x74, // "attStmt"
0xa2, // map(2)
0x63, // text(3)
0x78, 0x35, 0x63, // "x5c"
0x81, // array(1)
0x59, 0x02, 0xdd, // bytes(733)
0x30, 0x82, 0x02, 0xd9, 0x30, 0x82, 0x01, 0xc1, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09,
0x00, 0xdf, 0x92, 0xd9, 0xc4, 0xe2, 0xed, 0x66, 0x0a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x2e, 0x31, 0x2c, 0x30, 0x2a,
0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55,
0x32, 0x46, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69,
0x61, 0x6c, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17,
0x0d, 0x31, 0x34, 0x30, 0x38, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18,
0x0f, 0x32, 0x30, 0x35, 0x30, 0x30, 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x5a, 0x30, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x59, 0x75, 0x62,
0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b,
0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72,
0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x28, 0x30,
0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1f, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20,
0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x31,
0x31, 0x35, 0x35, 0x31, 0x30, 0x39, 0x35, 0x39, 0x39, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07,
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03,
0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x0a, 0x18, 0x6c, 0x6e, 0x4d, 0x0a, 0x6a, 0x52, 0x8a,
0x44, 0x90, 0x9a, 0x7a, 0x24, 0x23, 0x68, 0x70, 0x28, 0xd4, 0xc5, 0x7e, 0xcc, 0xb7, 0x17,
0xba, 0x12, 0x80, 0xb8, 0x5c, 0x2f, 0xc1, 0xe4, 0xe0, 0x61, 0x66, 0x8c, 0x3c, 0x20, 0xae,
0xf3, 0x33, 0x50, 0xd1, 0x96, 0x45, 0x23, 0x8a, 0x2c, 0x39, 0x0b, 0xf5, 0xdf, 0xfa, 0x34,
0xff, 0x25, 0x50, 0x2f, 0x47, 0x0f, 0x3d, 0x40, 0xb8, 0x88, 0xa3, 0x81, 0x81, 0x30, 0x7f,
0x30, 0x13, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x0d, 0x01, 0x04,
0x05, 0x04, 0x03, 0x05, 0x04, 0x03, 0x30, 0x22, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01,
0x82, 0xc4, 0x0a, 0x02, 0x04, 0x15, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34,
0x2e, 0x31, 0x2e, 0x34, 0x31, 0x34, 0x38, 0x32, 0x2e, 0x31, 0x2e, 0x37, 0x30, 0x13, 0x06,
0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03,
0x02, 0x04, 0x30, 0x30, 0x21, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c,
0x01, 0x01, 0x04, 0x04, 0x12, 0x04, 0x10, 0x2f, 0xc0, 0x57, 0x9f, 0x81, 0x13, 0x47, 0xea,
0xb1, 0x16, 0xbb, 0x5a, 0x8d, 0xb9, 0x20, 0x2a, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x82, 0xac, 0xaf,
0x11, 0x30, 0xa9, 0x9b, 0xd1, 0x43, 0x27, 0xd2, 0xf8, 0xf9, 0xb0, 0x41, 0xa2, 0xa0, 0x4a,
0x66, 0x85, 0x27, 0x24, 0x22, 0xe5, 0x7b, 0x14, 0xb0, 0xb8, 0xf8, 0x3b, 0x6f, 0x15, 0x45,
0x66, 0x4b, 0xbf, 0x55, 0x68, 0x1e, 0xaf, 0x01, 0x58, 0x72, 0x2a, 0xbf, 0xce, 0xd2, 0xe4,
0xac, 0x63, 0x3c, 0xec, 0x09, 0x59, 0x56, 0x45, 0x24, 0xb0, 0xf2, 0xe5, 0x17, 0xdd, 0x97,
0x10, 0x98, 0xb9, 0x89, 0x15, 0x17, 0xec, 0xd0, 0xc5, 0x53, 0xa2, 0xe4, 0x73, 0x9f, 0x9d,
0xe1, 0x3d, 0xaf, 0xd0, 0xd5, 0xd7, 0xb8, 0xac, 0x4a, 0x37, 0xf4, 0xf2, 0xcc, 0x30, 0xef,
0x25, 0xcb, 0x00, 0x65, 0x2d, 0x19, 0xdb, 0x69, 0xd7, 0xda, 0x57, 0xbd, 0x1a, 0x9c, 0x1d,
0x8e, 0xd8, 0x7d, 0x46, 0xd8, 0x0d, 0x2b, 0x3b, 0xdf, 0xd1, 0xd9, 0xef, 0x9d, 0x2b, 0x68,
0x32, 0xd4, 0xad, 0x5b, 0xcd, 0x74, 0x21, 0x4c, 0xe6, 0xa6, 0x14, 0x1d, 0x16, 0xb2, 0xe9,
0x3a, 0xcb, 0x2c, 0x88, 0xf6, 0x0a, 0x3e, 0xb6, 0xd5, 0xf6, 0x14, 0x71, 0x97, 0x59, 0x09,
0x37, 0x3b, 0xc6, 0x77, 0x90, 0x23, 0x24, 0x57, 0x1a, 0x57, 0x3f, 0x60, 0xf0, 0x7b, 0xbe,
0xd1, 0x7b, 0x92, 0xc8, 0xb5, 0x9f, 0xa2, 0x82, 0x10, 0xbf, 0xa8, 0xc6, 0x01, 0x22, 0x93,
0x00, 0x1b, 0x39, 0xef, 0xe5, 0x7b, 0xf9, 0xcb, 0x1e, 0x3a, 0xca, 0x8a, 0x41, 0x30, 0xf8,
0x3a, 0xf8, 0x66, 0x8f, 0x73, 0xde, 0xf2, 0x71, 0x1b, 0x20, 0xdc, 0x99, 0xe8, 0xa8, 0x04,
0xee, 0xa3, 0xf7, 0x42, 0x71, 0x97, 0xb6, 0xb4, 0x51, 0xb3, 0x73, 0x5c, 0x23, 0xbc, 0x9b,
0x1b, 0xe2, 0x74, 0xc2, 0x6d, 0x3b, 0xf9, 0x19, 0x6f, 0x8c, 0x4a, 0x4b, 0x71, 0x5f, 0x4b,
0x95, 0xc4, 0xdb, 0x7b, 0x97, 0xe7, 0x59, 0x4e, 0xb4, 0x65, 0x64, 0x8c, 0x1c, 0x63, 0x73,
0x69, 0x67, // "sig"
0x58, 0x46, // bytes(70)
0x30, 0x44, 0x02, 0x20, 0x48, 0x5a, 0x72, 0x40, 0xdf, 0x2c, 0x1e, 0x31, 0xa5, 0xb3, 0x0b,
0x3b, 0x2c, 0xd1, 0xad, 0xd0, 0x8d, 0xae, 0x8d, 0x7a, 0x25, 0x3e, 0xf5, 0xa6, 0x25, 0xdb,
0x2e, 0x22, 0x1b, 0x71, 0xe5, 0x78, 0x02, 0x20, 0x45, 0xbd, 0xdc, 0x30, 0xde, 0xf4, 0x05,
0x97, 0x5c, 0xac, 0x72, 0x58, 0x96, 0xa6, 0x00, 0x94, 0x57, 0x3a, 0xa5, 0xe8, 0x1e, 0xf4,
0xfd, 0x30, 0xd3, 0x88, 0x11, 0x8b, 0x49, 0x97, 0xdf, 0x34,
];
const SAMPLE_ATTESTATION_OBJ_PACKED: [u8; 677] = [
0xa3, // map(3)
0x63, // text(3)
0x66, 0x6D, 0x74, // "fmt"
0x66, // text(6)
0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, // "packed"
0x67, // text(7)
0x61, 0x74, 0x74, 0x53, 0x74, 0x6D, 0x74, // "attStmt"
0xa3, // map(3)
0x63, // text(3)
0x61, 0x6c, 0x67, // "alg"
0x26, // -7 (ES256)
0x63, // text(3)
0x73, 0x69, 0x67, // "sig"
0x58, 0x47, // bytes(71)
0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, 0xc1, 0x5c,
0xc9, // signature
0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, 0x62, 0xd5, 0xf0, 0x56, 0x12,
0x35, // ..
0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, 0x02, 0x21, 0x00, 0x90, 0x35, 0x7f, 0xf9, 0x10,
0xcc, // ..
0xb5, 0x6a, 0xc5, 0xb5, 0x96, 0x51, 0x19, 0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2,
0xb7, // ..
0x99, 0x59, 0x94, 0x80, 0x78, 0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29, // ..
0x63, // text(3)
0x78, 0x35, 0x63, // "x5c"
0x81, // array(1)
0x59, 0x01, 0x97, // bytes(407)
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, //certificate...
0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b,
0x4c, 0x29, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30,
0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63,
0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b,
0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72,
0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x1e, 0x17,
0x0d, 0x31, 0x36, 0x31, 0x32, 0x30, 0x34, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17,
0x0d, 0x32, 0x36, 0x31, 0x32, 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30,
0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75, 0x62, 0x69, 0x63,
0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b,
0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72,
0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, 0x30,
0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48,
0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52,
0xe5, 0x3a, 0xd5, 0xdf, 0xed, 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4, 0xe1, 0xaf,
0x8f, 0x22, 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13, 0xc3, 0xd5, 0x04, 0xff,
0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96, 0xc4, 0x4c, 0xb4, 0x84, 0x99, 0x79,
0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8, 0x60, 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d,
0x30, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a,
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, 0x46,
0x02, 0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, 0xf7, 0x37, 0x3e, 0x10,
0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, 0xd0, 0xc0, 0x3f, 0x3f, 0xda, 0x1f, 0xd2,
0x2d, 0xb3, 0xd0, 0x30, 0xe7, 0x02, 0x21, 0x00, 0xc4, 0xfa, 0xec, 0x34, 0x45, 0xa8, 0x20,
0xcf, 0x43, 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe, 0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, 0xc5,
0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d, 0xa2, 0x37, 0x23, 0xf3, 0x68, // text(8)
0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, // "authData"
0x58, 0x94, // bytes(148)
// authData
0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84,
0x27, // rp_id_hash
0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a,
0x87, // rp_id_hash
0x05, 0x1d, // rp_id_hash
0x41, // authData Flags
0x00, 0x00, 0x00, 0x0b, // authData counter
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d, // AAGUID
0x00, 0x10, // credential id length
0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c,
0x6f, // credential id
// credential public key
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1,
0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97,
0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20,
0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c,
0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e,
0xa6, 0x1c,
];
const SAMPLE_CERT_CHAIN: [u8; 709] = [
0x81, 0x59, 0x2, 0xc1, 0x30, 0x82, 0x2, 0xbd, 0x30, 0x82, 0x1, 0xa5, 0xa0, 0x3, 0x2, 0x1,
0x2, 0x2, 0x4, 0x18, 0xac, 0x46, 0xc0, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7,
0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x30, 0x2e, 0x31, 0x2c, 0x30, 0x2a, 0x6, 0x3, 0x55, 0x4, 0x3,
0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6f,
0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x35,
0x37, 0x32, 0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0xd, 0x31, 0x34, 0x30, 0x38,
0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0xf, 0x32, 0x30, 0x35, 0x30,
0x30, 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6e, 0x31, 0xb,
0x30, 0x9, 0x6, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, 0x6,
0x3, 0x55, 0x4, 0xa, 0xc, 0x9, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, 0x31,
0x22, 0x30, 0x20, 0x6, 0x3, 0x55, 0x4, 0xb, 0xc, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e,
0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x6, 0x3, 0x55, 0x4, 0x3, 0xc, 0x1e, 0x59,
0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65,
0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x31, 0x33, 0x39, 0x34, 0x33, 0x34, 0x38, 0x38, 0x30,
0x59, 0x30, 0x13, 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, 0x1, 0x6, 0x8, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x79, 0xea, 0x3b, 0x2c, 0x7c, 0x49,
0x70, 0x10, 0x62, 0x23, 0xc, 0xd2, 0x3f, 0xeb, 0x60, 0xe5, 0x29, 0x31, 0x71, 0xd4, 0x83,
0xf1, 0x0, 0xbe, 0x85, 0x9d, 0x6b, 0xf, 0x83, 0x97, 0x3, 0x1, 0xb5, 0x46, 0xcd, 0xd4, 0x6e,
0xcf, 0xca, 0xe3, 0xe3, 0xf3, 0xf, 0x81, 0xe9, 0xed, 0x62, 0xbd, 0x26, 0x8d, 0x4c, 0x1e,
0xbd, 0x37, 0xb3, 0xbc, 0xbe, 0x92, 0xa8, 0xc2, 0xae, 0xeb, 0x4e, 0x3a, 0xa3, 0x6c, 0x30,
0x6a, 0x30, 0x22, 0x6, 0x9, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xc4, 0xa, 0x2, 0x4, 0x15,
0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, 0x34,
0x38, 0x32, 0x2e, 0x31, 0x2e, 0x37, 0x30, 0x13, 0x6, 0xb, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82,
0xe5, 0x1c, 0x2, 0x1, 0x1, 0x4, 0x4, 0x3, 0x2, 0x5, 0x20, 0x30, 0x21, 0x6, 0xb, 0x2b, 0x6,
0x1, 0x4, 0x1, 0x82, 0xe5, 0x1c, 0x1, 0x1, 0x4, 0x4, 0x12, 0x4, 0x10, 0xcb, 0x69, 0x48,
0x1e, 0x8f, 0xf7, 0x40, 0x39, 0x93, 0xec, 0xa, 0x27, 0x29, 0xa1, 0x54, 0xa8, 0x30, 0xc,
0x6, 0x3, 0x55, 0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x2, 0x30, 0x0, 0x30, 0xd, 0x6, 0x9, 0x2a,
0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x3, 0x82, 0x1, 0x1, 0x0, 0x97, 0x9d,
0x3, 0x97, 0xd8, 0x60, 0xf8, 0x2e, 0xe1, 0x5d, 0x31, 0x1c, 0x79, 0x6e, 0xba, 0xfb, 0x22,
0xfa, 0xa7, 0xe0, 0x84, 0xd9, 0xba, 0xb4, 0xc6, 0x1b, 0xbb, 0x57, 0xf3, 0xe6, 0xb4, 0xc1,
0x8a, 0x48, 0x37, 0xb8, 0x5c, 0x3c, 0x4e, 0xdb, 0xe4, 0x83, 0x43, 0xf4, 0xd6, 0xa5, 0xd9,
0xb1, 0xce, 0xda, 0x8a, 0xe1, 0xfe, 0xd4, 0x91, 0x29, 0x21, 0x73, 0x5, 0x8e, 0x5e, 0xe1,
0xcb, 0xdd, 0x6b, 0xda, 0xc0, 0x75, 0x57, 0xc6, 0xa0, 0xe8, 0xd3, 0x68, 0x25, 0xba, 0x15,
0x9e, 0x7f, 0xb5, 0xad, 0x8c, 0xda, 0xf8, 0x4, 0x86, 0x8c, 0xf9, 0xe, 0x8f, 0x1f, 0x8a,
0xea, 0x17, 0xc0, 0x16, 0xb5, 0x5c, 0x2a, 0x7a, 0xd4, 0x97, 0xc8, 0x94, 0xfb, 0x71, 0xd7,
0x53, 0xd7, 0x9b, 0x9a, 0x48, 0x4b, 0x6c, 0x37, 0x6d, 0x72, 0x3b, 0x99, 0x8d, 0x2e, 0x1d,
0x43, 0x6, 0xbf, 0x10, 0x33, 0xb5, 0xae, 0xf8, 0xcc, 0xa5, 0xcb, 0xb2, 0x56, 0x8b, 0x69,
0x24, 0x22, 0x6d, 0x22, 0xa3, 0x58, 0xab, 0x7d, 0x87, 0xe4, 0xac, 0x5f, 0x2e, 0x9, 0x1a,
0xa7, 0x15, 0x79, 0xf3, 0xa5, 0x69, 0x9, 0x49, 0x7d, 0x72, 0xf5, 0x4e, 0x6, 0xba, 0xc1,
0xc3, 0xb4, 0x41, 0x3b, 0xba, 0x5e, 0xaf, 0x94, 0xc3, 0xb6, 0x4f, 0x34, 0xf9, 0xeb, 0xa4,
0x1a, 0xcb, 0x6a, 0xe2, 0x83, 0x77, 0x6d, 0x36, 0x46, 0x53, 0x78, 0x48, 0xfe, 0xe8, 0x84,
0xbd, 0xdd, 0xf5, 0xb1, 0xba, 0x57, 0x98, 0x54, 0xcf, 0xfd, 0xce, 0xba, 0xc3, 0x44, 0x5,
0x95, 0x27, 0xe5, 0x6d, 0xd5, 0x98, 0xf8, 0xf5, 0x66, 0x71, 0x5a, 0xbe, 0x43, 0x1, 0xdd,
0x19, 0x11, 0x30, 0xe6, 0xb9, 0xf0, 0xc6, 0x40, 0x39, 0x12, 0x53, 0xe2, 0x29, 0x80, 0x3f,
0x3a, 0xef, 0x27, 0x4b, 0xed, 0xbf, 0xde, 0x3f, 0xcb, 0xbd, 0x42, 0xea, 0xd6, 0x79,
];
const SAMPLE_AUTH_DATA_MAKE_CREDENTIAL: [u8; 164] = [
0x58, 0xA2, // bytes(162)
// authData
0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84,
0x27, // rp_id_hash
0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a,
0x87, // rp_id_hash
0x05, 0x1d, // rp_id_hash
0xC1, // authData Flags
0x00, 0x00, 0x00, 0x0b, // authData counter
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d, // AAGUID
0x00, 0x10, // credential id length
0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c,
0x6f, // credential id
// credential public key
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1,
0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97,
0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20,
0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c,
0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e,
0xa6, 0x1c, // pub key end
// Extensions
0xA1, // map(1)
0x6B, // text(11)
0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret"
0xF5, // true
];
const SAMPLE_AUTH_DATA_GET_ASSERTION: [u8; 229] = [
0x58, 0xE3, // bytes(227)
// authData
0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84,
0x27, // rp_id_hash
0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a,
0x87, // rp_id_hash
0x05, 0x1d, // rp_id_hash
0xC1, // authData Flags
0x00, 0x00, 0x00, 0x0b, // authData counter
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d, // AAGUID
0x00, 0x10, // credential id length
0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c,
0x6f, // credential id
// credential public key
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1,
0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97,
0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20,
0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c,
0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e,
0xa6, 0x1c, // pub key end
// Extensions
0xA1, // map(1)
0x6B, // text(11)
0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret"
0x58, 0x40, // bytes(64)
0x1F, 0x91, 0x52, 0x6C, 0xAE, 0x45, 0x6E, 0x4C, 0xBB, 0x71, 0xC4, 0xDD, 0xE7, 0xBB, 0x87,
0x71, 0x57, 0xE6, 0xE5, 0x4D, 0xFE, 0xD3, 0x01, 0x5D, 0x7D, 0x4D, 0xBB, 0x22, 0x69, 0xAF,
0xCD, 0xE6, 0xA9, 0x1B, 0x8D, 0x26, 0x7E, 0xBB, 0xF8, 0x48, 0xEB, 0x95, 0xA6, 0x8E, 0x79,
0xC7, 0xAC, 0x70, 0x5E, 0x35, 0x1D, 0x54, 0x3D, 0xB0, 0x16, 0x58, 0x87, 0xD6, 0x29, 0x0F,
0xD4, 0x7A, 0x40, 0xC4,
];
pub fn create_attestation_obj() -> AttestationObject {
AttestationObject {
auth_data: AuthenticatorData {
rp_id_hash: RpIdHash::from(&[
0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d,
0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65,
0xbe, 0x59, 0x7a, 0x87, 0x5, 0x1d,
])
.unwrap(),
flags: AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::ATTESTED,
counter: 11,
credential_data: Some(AttestedCredentialData {
aaguid: AAGuid::from(&[
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11,
0x1f, 0x9e, 0xdc, 0x7d,
])
.unwrap(),
credential_id: vec![
0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6,
0xd9, 0x43, 0x5c, 0x6f,
],
credential_public_key: COSEKey {
alg: COSEAlgorithm::ES256,
key: COSEKeyType::EC2(COSEEC2Key {
curve: Curve::SECP256R1,
x: vec![
0xA5, 0xFD, 0x5C, 0xE1, 0xB1, 0xC4, 0x58, 0xC5, 0x30, 0xA5, 0x4F,
0xA6, 0x1B, 0x31, 0xBF, 0x6B, 0x04, 0xBE, 0x8B, 0x97, 0xAF, 0xDE,
0x54, 0xDD, 0x8C, 0xBB, 0x69, 0x27, 0x5A, 0x8A, 0x1B, 0xE1,
],
y: vec![
0xFA, 0x3A, 0x32, 0x31, 0xDD, 0x9D, 0xEE, 0xD9, 0xD1, 0x89, 0x7B,
0xE5, 0xA6, 0x22, 0x8C, 0x59, 0x50, 0x1E, 0x4B, 0xCD, 0x12, 0x97,
0x5D, 0x3D, 0xFF, 0x73, 0x0F, 0x01, 0x27, 0x8E, 0xA6, 0x1C,
],
}),
},
}),
extensions: Default::default(),
},
att_stmt: AttestationStatement::Packed(AttestationStatementPacked {
alg: COSEAlgorithm::ES256,
sig: Signature(vec![
0x30, 0x45, 0x02, 0x20, 0x13, 0xf7, 0x3c, 0x5d, 0x9d, 0x53, 0x0e, 0x8c, 0xc1,
0x5c, 0xc9, 0xbd, 0x96, 0xad, 0x58, 0x6d, 0x39, 0x36, 0x64, 0xe4, 0x62, 0xd5,
0xf0, 0x56, 0x12, 0x35, 0xe6, 0x35, 0x0f, 0x2b, 0x72, 0x89, 0x02, 0x21, 0x00,
0x90, 0x35, 0x7f, 0xf9, 0x10, 0xcc, 0xb5, 0x6a, 0xc5, 0xb5, 0x96, 0x51, 0x19,
0x48, 0x58, 0x1c, 0x8f, 0xdd, 0xb4, 0xa2, 0xb7, 0x99, 0x59, 0x94, 0x80, 0x78,
0xb0, 0x9f, 0x4b, 0xdc, 0x62, 0x29,
]),
attestation_cert: vec![AttestationCertificate(vec![
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02,
0x02, 0x09, 0x00, 0x85, 0x9b, 0x72, 0x6c, 0xb2, 0x4b, 0x4c, 0x29, 0x30, 0x0a,
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x47, 0x31,
0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75, 0x62,
0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06,
0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x32,
0x30, 0x34, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x36,
0x31, 0x32, 0x30, 0x32, 0x31, 0x31, 0x35, 0x35, 0x30, 0x30, 0x5a, 0x30, 0x47,
0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x59, 0x75,
0x62, 0x69, 0x63, 0x6f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20,
0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e,
0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xad, 0x11, 0xeb, 0x0e, 0x88, 0x52,
0xe5, 0x3a, 0xd5, 0xdf, 0xed, 0x86, 0xb4, 0x1e, 0x61, 0x34, 0xa1, 0x8e, 0xc4,
0xe1, 0xaf, 0x8f, 0x22, 0x1a, 0x3c, 0x7d, 0x6e, 0x63, 0x6c, 0x80, 0xea, 0x13,
0xc3, 0xd5, 0x04, 0xff, 0x2e, 0x76, 0x21, 0x1b, 0xb4, 0x45, 0x25, 0xb1, 0x96,
0xc4, 0x4c, 0xb4, 0x84, 0x99, 0x79, 0xcf, 0x6f, 0x89, 0x6e, 0xcd, 0x2b, 0xb8,
0x60, 0xde, 0x1b, 0xf4, 0x37, 0x6b, 0xa3, 0x0d, 0x30, 0x0b, 0x30, 0x09, 0x06,
0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0a, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, 0x46, 0x02,
0x21, 0x00, 0xe9, 0xa3, 0x9f, 0x1b, 0x03, 0x19, 0x75, 0x25, 0xf7, 0x37, 0x3e,
0x10, 0xce, 0x77, 0xe7, 0x80, 0x21, 0x73, 0x1b, 0x94, 0xd0, 0xc0, 0x3f, 0x3f,
0xda, 0x1f, 0xd2, 0x2d, 0xb3, 0xd0, 0x30, 0xe7, 0x02, 0x21, 0x00, 0xc4, 0xfa,
0xec, 0x34, 0x45, 0xa8, 0x20, 0xcf, 0x43, 0x12, 0x9c, 0xdb, 0x00, 0xaa, 0xbe,
0xfd, 0x9a, 0xe2, 0xd8, 0x74, 0xf9, 0xc5, 0xd3, 0x43, 0xcb, 0x2f, 0x11, 0x3d,
0xa2, 0x37, 0x23, 0xf3,
])],
}),
}
}
#[test]
fn parse_cert_chain() {
let cert: AttestationCertificate = from_slice(&SAMPLE_CERT_CHAIN[1..]).unwrap();
assert_eq!(&cert.0, &SAMPLE_CERT_CHAIN[4..]);
let _cert: Vec<AttestationCertificate> = from_slice(&SAMPLE_CERT_CHAIN).unwrap();
}
#[test]
fn parse_attestation_statement() {
let actual: AttestationStatement = from_slice(&SAMPLE_ATTESTATION_STMT_NONE).unwrap();
let expected = AttestationStatement::None;
assert_eq!(expected, actual);
let actual: AttestationStatement = from_slice(&SAMPLE_ATTESTATION_STMT_FIDO_U2F).unwrap();
let expected = AttestationStatement::FidoU2F(AttestationStatementFidoU2F {
attestation_cert: vec![AttestationCertificate(vec![
0x30, 0x82, 0x02, 0xd9, 0x30, 0x82, 0x01, 0xc1, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02,
0x09, 0x00, 0xdf, 0x92, 0xd9, 0xc4, 0xe2, 0xed, 0x66, 0x0a, 0x30, 0x0d, 0x06, 0x09,
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x2e, 0x31,
0x2c, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x23, 0x59, 0x75, 0x62, 0x69,
0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30, 0x30,
0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x38, 0x30, 0x31, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x30, 0x35, 0x30, 0x30, 0x39,
0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6f, 0x31, 0x0b, 0x30,
0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10,
0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20,
0x41, 0x42, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x19, 0x41,
0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41,
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x28, 0x30, 0x26,
0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1f, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20,
0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20,
0x31, 0x31, 0x35, 0x35, 0x31, 0x30, 0x39, 0x35, 0x39, 0x39, 0x30, 0x59, 0x30, 0x13,
0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48,
0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x0a, 0x18, 0x6c, 0x6e, 0x4d,
0x0a, 0x6a, 0x52, 0x8a, 0x44, 0x90, 0x9a, 0x7a, 0x24, 0x23, 0x68, 0x70, 0x28, 0xd4,
0xc5, 0x7e, 0xcc, 0xb7, 0x17, 0xba, 0x12, 0x80, 0xb8, 0x5c, 0x2f, 0xc1, 0xe4, 0xe0,
0x61, 0x66, 0x8c, 0x3c, 0x20, 0xae, 0xf3, 0x33, 0x50, 0xd1, 0x96, 0x45, 0x23, 0x8a,
0x2c, 0x39, 0x0b, 0xf5, 0xdf, 0xfa, 0x34, 0xff, 0x25, 0x50, 0x2f, 0x47, 0x0f, 0x3d,
0x40, 0xb8, 0x88, 0xa3, 0x81, 0x81, 0x30, 0x7f, 0x30, 0x13, 0x06, 0x0a, 0x2b, 0x06,
0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x0d, 0x01, 0x04, 0x05, 0x04, 0x03, 0x05, 0x04,
0x03, 0x30, 0x22, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x02,
0x04, 0x15, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e,
0x34, 0x31, 0x34, 0x38, 0x32, 0x2e, 0x31, 0x2e, 0x37, 0x30, 0x13, 0x06, 0x0b, 0x2b,
0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02,
0x04, 0x30, 0x30, 0x21, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c,
0x01, 0x01, 0x04, 0x04, 0x12, 0x04, 0x10, 0x2f, 0xc0, 0x57, 0x9f, 0x81, 0x13, 0x47,
0xea, 0xb1, 0x16, 0xbb, 0x5a, 0x8d, 0xb9, 0x20, 0x2a, 0x30, 0x0c, 0x06, 0x03, 0x55,
0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a,
0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
0x00, 0x82, 0xac, 0xaf, 0x11, 0x30, 0xa9, 0x9b, 0xd1, 0x43, 0x27, 0xd2, 0xf8, 0xf9,
0xb0, 0x41, 0xa2, 0xa0, 0x4a, 0x66, 0x85, 0x27, 0x24, 0x22, 0xe5, 0x7b, 0x14, 0xb0,
0xb8, 0xf8, 0x3b, 0x6f, 0x15, 0x45, 0x66, 0x4b, 0xbf, 0x55, 0x68, 0x1e, 0xaf, 0x01,
0x58, 0x72, 0x2a, 0xbf, 0xce, 0xd2, 0xe4, 0xac, 0x63, 0x3c, 0xec, 0x09, 0x59, 0x56,
0x45, 0x24, 0xb0, 0xf2, 0xe5, 0x17, 0xdd, 0x97, 0x10, 0x98, 0xb9, 0x89, 0x15, 0x17,
0xec, 0xd0, 0xc5, 0x53, 0xa2, 0xe4, 0x73, 0x9f, 0x9d, 0xe1, 0x3d, 0xaf, 0xd0, 0xd5,
0xd7, 0xb8, 0xac, 0x4a, 0x37, 0xf4, 0xf2, 0xcc, 0x30, 0xef, 0x25, 0xcb, 0x00, 0x65,
0x2d, 0x19, 0xdb, 0x69, 0xd7, 0xda, 0x57, 0xbd, 0x1a, 0x9c, 0x1d, 0x8e, 0xd8, 0x7d,
0x46, 0xd8, 0x0d, 0x2b, 0x3b, 0xdf, 0xd1, 0xd9, 0xef, 0x9d, 0x2b, 0x68, 0x32, 0xd4,
0xad, 0x5b, 0xcd, 0x74, 0x21, 0x4c, 0xe6, 0xa6, 0x14, 0x1d, 0x16, 0xb2, 0xe9, 0x3a,
0xcb, 0x2c, 0x88, 0xf6, 0x0a, 0x3e, 0xb6, 0xd5, 0xf6, 0x14, 0x71, 0x97, 0x59, 0x09,
0x37, 0x3b, 0xc6, 0x77, 0x90, 0x23, 0x24, 0x57, 0x1a, 0x57, 0x3f, 0x60, 0xf0, 0x7b,
0xbe, 0xd1, 0x7b, 0x92, 0xc8, 0xb5, 0x9f, 0xa2, 0x82, 0x10, 0xbf, 0xa8, 0xc6, 0x01,
0x22, 0x93, 0x00, 0x1b, 0x39, 0xef, 0xe5, 0x7b, 0xf9, 0xcb, 0x1e, 0x3a, 0xca, 0x8a,
0x41, 0x30, 0xf8, 0x3a, 0xf8, 0x66, 0x8f, 0x73, 0xde, 0xf2, 0x71, 0x1b, 0x20, 0xdc,
0x99, 0xe8, 0xa8, 0x04, 0xee, 0xa3, 0xf7, 0x42, 0x71, 0x97, 0xb6, 0xb4, 0x51, 0xb3,
0x73, 0x5c, 0x23, 0xbc, 0x9b, 0x1b, 0xe2, 0x74, 0xc2, 0x6d, 0x3b, 0xf9, 0x19, 0x6f,
0x8c, 0x4a, 0x4b, 0x71, 0x5f, 0x4b, 0x95, 0xc4, 0xdb, 0x7b, 0x97, 0xe7, 0x59, 0x4e,
0xb4, 0x65, 0x64, 0x8c, 0x1c,
])],
sig: Signature(vec![
0x30, 0x44, 0x02, 0x20, 0x48, 0x5a, 0x72, 0x40, 0xdf, 0x2c, 0x1e, 0x31, 0xa5, 0xb3,
0x0b, 0x3b, 0x2c, 0xd1, 0xad, 0xd0, 0x8d, 0xae, 0x8d, 0x7a, 0x25, 0x3e, 0xf5, 0xa6,
0x25, 0xdb, 0x2e, 0x22, 0x1b, 0x71, 0xe5, 0x78, 0x02, 0x20, 0x45, 0xbd, 0xdc, 0x30,
0xde, 0xf4, 0x05, 0x97, 0x5c, 0xac, 0x72, 0x58, 0x96, 0xa6, 0x00, 0x94, 0x57, 0x3a,
0xa5, 0xe8, 0x1e, 0xf4, 0xfd, 0x30, 0xd3, 0x88, 0x11, 0x8b, 0x49, 0x97, 0xdf, 0x34,
]),
});
assert_eq!(expected, actual);
}
#[test]
fn parse_attestation_object() {
let actual: AttestationObject = from_slice(&SAMPLE_ATTESTATION_OBJ_PACKED).unwrap();
let expected = create_attestation_obj();
assert_eq!(expected, actual);
}
#[test]
fn test_anonymize_att_obj() {
// Anonymize should prevent identifying data in the attestation statement from being
// serialized.
let mut att_obj = create_attestation_obj();
// This test assumes that the sample attestation object contains identifying information
assert_ne!(att_obj.att_stmt, AttestationStatement::None);
assert_ne!(
att_obj
.auth_data
.credential_data
.as_ref()
.expect("credential_data should be Some")
.aaguid,
AAGuid::default()
);
att_obj.anonymize();
// Write the attestation object out to bytes and read it back. The result should not
// have an attestation statement, and it should have the default AAGUID.
let encoded_att_obj = to_vec(&att_obj).expect("could not serialize anonymized att_obj");
let att_obj: AttestationObject =
from_slice(&encoded_att_obj).expect("could not deserialize anonymized att_obj");
assert_eq!(att_obj.att_stmt, AttestationStatement::None);
assert_eq!(
att_obj
.auth_data
.credential_data
.as_ref()
.expect("credential_data should be Some")
.aaguid,
AAGuid::default()
);
}
#[test]
fn parse_reader() {
let v: Vec<u8> = vec![
0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72,
];
let mut data = Cursor::new(v);
let value: String = from_slice_stream::<_, _, serde_cbor::Error>(&mut data).unwrap();
assert_eq!(value, "foobar");
let mut remaining = Vec::new();
data.read_to_end(&mut remaining).unwrap();
assert_eq!(
remaining.as_slice(),
&[0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]
);
let mut data = Cursor::new(remaining);
let value: String = from_slice_stream::<_, _, serde_cbor::Error>(&mut data).unwrap();
assert_eq!(value, "foobar");
let mut remaining = Vec::new();
data.read_to_end(&mut remaining).unwrap();
assert!(remaining.is_empty());
}
#[test]
fn parse_extensions() {
let auth_make: AuthenticatorData = from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL).unwrap();
assert_eq!(
auth_make.extensions.hmac_secret,
Some(HmacSecretResponse::Confirmed(true))
);
let auth_get: AuthenticatorData = from_slice(&SAMPLE_AUTH_DATA_GET_ASSERTION).unwrap();
assert_eq!(
auth_get.extensions.hmac_secret,
Some(HmacSecretResponse::Secret(vec![
0x1F, 0x91, 0x52, 0x6C, 0xAE, 0x45, 0x6E, 0x4C, 0xBB, 0x71, 0xC4, 0xDD, 0xE7, 0xBB,
0x87, 0x71, 0x57, 0xE6, 0xE5, 0x4D, 0xFE, 0xD3, 0x01, 0x5D, 0x7D, 0x4D, 0xBB, 0x22,
0x69, 0xAF, 0xCD, 0xE6, 0xA9, 0x1B, 0x8D, 0x26, 0x7E, 0xBB, 0xF8, 0x48, 0xEB, 0x95,
0xA6, 0x8E, 0x79, 0xC7, 0xAC, 0x70, 0x5E, 0x35, 0x1D, 0x54, 0x3D, 0xB0, 0x16, 0x58,
0x87, 0xD6, 0x29, 0x0F, 0xD4, 0x7A, 0x40, 0xC4,
]))
);
}
#[test]
fn test_empty_extension_data() {
let mut parsed_auth_data: AuthenticatorData =
from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL).unwrap();
assert!(parsed_auth_data
.flags
.contains(AuthenticatorDataFlags::EXTENSION_DATA));
// Remove the extension data but keep the extension data flag set.
parsed_auth_data.extensions = Default::default();
let with_flag = to_vec(&parsed_auth_data).expect("could not serialize auth data");
// The serialized auth data should end with an empty map (CBOR 0xA0).
assert_eq!(with_flag[with_flag.len() - 1], 0xA0);
// Remove the extension data flag.
parsed_auth_data
.flags
.remove(AuthenticatorDataFlags::EXTENSION_DATA);
let without_flag = to_vec(&parsed_auth_data).expect("could not serialize auth data");
// The serialized auth data should be one byte shorter.
assert!(with_flag.len() == without_flag.len() + 1);
}
#[test]
fn test_aaguid_output() {
let input = [
0xcb, 0x69, 0x48, 0x1e, 0x8f, 0xf0, 0x00, 0x39, 0x93, 0xec, 0x0a, 0x27, 0x29, 0xa1,
0x54, 0xa8,
];
let expected = "AAGuid(cb69481e-8ff0-0039-93ec-0a2729a154a8)";
let result = AAGuid::from(&input).expect("Failed to parse AAGuid");
let res_str = format!("{result:?}");
assert_eq!(expected, &res_str);
}
#[test]
fn test_ad_flags_from_bits() {
// Check that AuthenticatorDataFlags is defined on the entire u8 range and that
// `from_bits_truncate` is lossless
for x in 0..=u8::MAX {
assert_eq!(
AuthenticatorDataFlags::from_bits(x),
Some(AuthenticatorDataFlags::from_bits_truncate(x))
);
}
}
#[test]
fn serialize_att_obj_none() -> Result<(), serde_cbor::Error> {
let att_stmt = AttestationStatement::None;
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({}))}]")
assert_eq!(serde_cbor::ser::to_vec(&att_stmt)?, [0xa0]);
let att_obj = AttestationObject {
auth_data: serde_cbor::de::from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL)?,
att_stmt,
};
assert_eq!(
serde_cbor::ser::to_vec(&att_obj)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"fmt": "none", "attStmt": {}, "authData": SAMPLE_AUTH_DATA_MAKE_CREDENTIAL[2:] }))}]")
[
0xa3, 0x63, 0x66, 0x6d, 0x74, 0x64, 0x6e, 0x6f, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x74,
0x53, 0x74, 0x6d, 0x74, 0xa0, 0x68, 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61,
0x58, 0xa2, 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4,
0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65,
0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0xc1, 0x00, 0x00, 0x00, 0x0b, 0xf8, 0xa0, 0x11,
0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00,
0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9,
0x43, 0x5c, 0x6f, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5,
0xfd, 0x5c, 0xe1, 0xb1, 0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf,
0x6b, 0x04, 0xbe, 0x8b, 0x97, 0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a,
0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, 0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9,
0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c, 0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97,
0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e, 0xa6, 0x1c, 0xa1, 0x6b, 0x68, 0x6d,
0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xf5
]
);
Ok(())
}
#[test]
fn serialize_att_obj_packed() -> Result<(), serde_cbor::Error> {
let att_stmt = AttestationStatement::Packed(AttestationStatementPacked {
alg: COSEAlgorithm::ES256,
sig: Signature(vec![1, 2, 3, 4]),
attestation_cert: vec![
AttestationCertificate(vec![5, 6, 7, 8]),
AttestationCertificate(vec![9, 10, 11, 12]),
],
});
assert_eq!(
serde_cbor::ser::to_vec(&att_stmt)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"alg":-7, "sig":bytes([1, 2, 3, 4]), "x5c": [bytes([5, 6, 7, 8]), bytes([9, 10, 11, 12])] }))}]")
[
0xa3, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x44, 0x01, 0x02, 0x03,
0x04, 0x63, 0x78, 0x35, 0x63, 0x82, 0x44, 0x05, 0x06, 0x07, 0x08, 0x44, 0x09, 0x0a,
0x0b, 0x0c
]
);
let att_obj = AttestationObject {
auth_data: serde_cbor::de::from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL)?,
att_stmt,
};
assert_eq!(
serde_cbor::ser::to_vec(&att_obj)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"fmt": "packed", "attStmt": {"alg":-7, "sig":bytes([1, 2, 3, 4]), "x5c": [bytes([5, 6, 7, 8]), bytes([9, 10, 11, 12])] }, "authData": SAMPLE_AUTH_DATA_MAKE_CREDENTIAL[2:] }))}]")
[
0xa3, 0x63, 0x66, 0x6d, 0x74, 0x66, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x67, 0x61,
0x74, 0x74, 0x53, 0x74, 0x6d, 0x74, 0xa3, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73,
0x69, 0x67, 0x44, 0x01, 0x02, 0x03, 0x04, 0x63, 0x78, 0x35, 0x63, 0x82, 0x44, 0x05,
0x06, 0x07, 0x08, 0x44, 0x09, 0x0a, 0x0b, 0x0c, 0x68, 0x61, 0x75, 0x74, 0x68, 0x44,
0x61, 0x74, 0x61, 0x58, 0xa2, 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34,
0x6a, 0xb4, 0xe4, 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25,
0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0xc1, 0x00, 0x00, 0x00, 0x0b,
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e,
0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a,
0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21,
0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1, 0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6,
0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97, 0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb,
0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, 0xfa, 0x3a, 0x32, 0x31, 0xdd,
0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c, 0x59, 0x50, 0x1e, 0x4b,
0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e, 0xa6, 0x1c, 0xa1,
0x6b, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xf5
]
);
Ok(())
}
#[test]
fn serialize_att_obj_fido_u2f() -> Result<(), serde_cbor::Error> {
let att_stmt = AttestationStatement::FidoU2F(AttestationStatementFidoU2F {
sig: Signature(vec![1, 2, 3, 4]),
attestation_cert: vec![
AttestationCertificate(vec![5, 6, 7, 8]),
AttestationCertificate(vec![9, 10, 11, 12]),
],
});
assert_eq!(
serde_cbor::ser::to_vec(&att_stmt)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"x5c": [bytes([5, 6, 7, 8]), bytes([9, 10, 11, 12])], "sig": bytes([1, 2, 3, 4]) }))}]")
[
0xa2, 0x63, 0x73, 0x69, 0x67, 0x44, 0x01, 0x02, 0x03, 0x04, 0x63, 0x78, 0x35, 0x63,
0x82, 0x44, 0x05, 0x06, 0x07, 0x08, 0x44, 0x09, 0x0a, 0x0b, 0x0c
]
);
let att_obj = AttestationObject {
auth_data: serde_cbor::de::from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL)?,
att_stmt,
};
assert_eq!(
serde_cbor::ser::to_vec(&att_obj)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"fmt": "fido-u2f", "attStmt": {"x5c": [bytes([5, 6, 7, 8]), bytes([9, 10, 11, 12])], "sig": bytes([1, 2, 3, 4]) }, "authData": SAMPLE_AUTH_DATA_MAKE_CREDENTIAL[2:] }))}]")
[
0xa3, 0x63, 0x66, 0x6d, 0x74, 0x68, 0x66, 0x69, 0x64, 0x6f, 0x2d, 0x75, 0x32, 0x66,
0x67, 0x61, 0x74, 0x74, 0x53, 0x74, 0x6d, 0x74, 0xa2, 0x63, 0x73, 0x69, 0x67, 0x44,
0x01, 0x02, 0x03, 0x04, 0x63, 0x78, 0x35, 0x63, 0x82, 0x44, 0x05, 0x06, 0x07, 0x08,
0x44, 0x09, 0x0a, 0x0b, 0x0c, 0x68, 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61,
0x58, 0xa2, 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4,
0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65,
0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0xc1, 0x00, 0x00, 0x00, 0x0b, 0xf8, 0xa0, 0x11,
0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00,
0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9,
0x43, 0x5c, 0x6f, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5,
0xfd, 0x5c, 0xe1, 0xb1, 0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf,
0x6b, 0x04, 0xbe, 0x8b, 0x97, 0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a,
0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, 0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9,
0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c, 0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97,
0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e, 0xa6, 0x1c, 0xa1, 0x6b, 0x68, 0x6d,
0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xf5
]
);
Ok(())
}
#[test]
fn serialize_att_obj_android_key() -> Result<(), serde_cbor::Error> {
let att_stmt = AttestationStatement::AndroidKey(serde_cbor::Value::Map(
vec![
("alg".to_string().into(), serde_cbor::Value::Integer(-7)),
(
"sig".to_string().into(),
serde_cbor::Value::Bytes(vec![1, 2, 3, 4]),
),
(
"x5c".to_string().into(),
serde_cbor::Value::Array(vec![
serde_cbor::Value::Bytes(vec![5, 6, 7, 8]),
serde_cbor::Value::Bytes(vec![9, 10, 11, 12]),
]),
),
]
.into_iter()
.collect(),
));
assert_eq!(
serde_cbor::ser::to_vec(&att_stmt)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"alg": -7, "sig": bytes([1, 2, 3, 4]), "x5c": [bytes([5, 6, 7, 8]), bytes([9, 10, 11, 12])] }))}]")
[
0xa3, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x44, 0x01, 0x02, 0x03,
0x04, 0x63, 0x78, 0x35, 0x63, 0x82, 0x44, 0x05, 0x06, 0x07, 0x08, 0x44, 0x09, 0x0a,
0x0b, 0x0c
]
);
let att_obj = AttestationObject {
auth_data: serde_cbor::de::from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL)?,
att_stmt,
};
assert_eq!(
serde_cbor::ser::to_vec(&att_obj)?,
// Python: fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({ "fmt": "android-key", "attStmt": {"alg": -7, "sig": bytes([1, 2, 3, 4]), "x5c": [bytes([5, 6, 7, 8]), bytes([9, 10, 11, 12])] }, "authData": SAMPLE_AUTH_DATA_MAKE_CREDENTIAL[2:] }))}]")
[
0xa3, 0x63, 0x66, 0x6d, 0x74, 0x6b, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2d,
0x6b, 0x65, 0x79, 0x67, 0x61, 0x74, 0x74, 0x53, 0x74, 0x6d, 0x74, 0xa3, 0x63, 0x61,
0x6c, 0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x44, 0x01, 0x02, 0x03, 0x04, 0x63, 0x78,
0x35, 0x63, 0x82, 0x44, 0x05, 0x06, 0x07, 0x08, 0x44, 0x09, 0x0a, 0x0b, 0x0c, 0x68,
0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x58, 0xa2, 0xc2, 0x89, 0xc5, 0xca,
0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d,
0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d,
0xc1, 0x00, 0x00, 0x00, 0x0b, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80,
0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b,
0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa5, 0x01, 0x02,
0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1, 0xc4, 0x58,
0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97, 0xaf,
0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20,
0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22,
0x8c, 0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01,
0x27, 0x8e, 0xa6, 0x1c, 0xa1, 0x6b, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63,
0x72, 0x65, 0x74, 0xf5
]
);
Ok(())
}
#[test]
fn serialize_att_obj_android_safetynet() -> Result<(), serde_cbor::Error> {
let att_stmt = AttestationStatement::AndroidSafetyNet(serde_cbor::Value::Map(
vec![
(
"ver".to_string().into(),
"Kom ihåg att du aldrig får snyta dig i mattan!"
.to_string()
.into(),
),
(
"response".to_string().into(),
serde_cbor::Value::Bytes(vec![1, 2, 3, 4]),
),
]
.into_iter()
.collect(),
));
assert_eq!(
serde_cbor::ser::to_vec(&att_stmt)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"ver": "Kom ihåg att du aldrig får snyta dig i mattan!", "response": bytes([1, 2, 3, 4]) }))}]")
[
0xa2, 0x63, 0x76, 0x65, 0x72, 0x78, 0x30, 0x4b, 0x6f, 0x6d, 0x20, 0x69, 0x68, 0xc3,
0xa5, 0x67, 0x20, 0x61, 0x74, 0x74, 0x20, 0x64, 0x75, 0x20, 0x61, 0x6c, 0x64, 0x72,
0x69, 0x67, 0x20, 0x66, 0xc3, 0xa5, 0x72, 0x20, 0x73, 0x6e, 0x79, 0x74, 0x61, 0x20,
0x64, 0x69, 0x67, 0x20, 0x69, 0x20, 0x6d, 0x61, 0x74, 0x74, 0x61, 0x6e, 0x21, 0x68,
0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x01, 0x02, 0x03, 0x04
]
);
let att_obj = AttestationObject {
auth_data: serde_cbor::de::from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL)?,
att_stmt,
};
assert_eq!(
serde_cbor::ser::to_vec(&att_obj)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({ "fmt": "android-safetynet", "attStmt": {"ver": "Kom ihåg att du aldrig får snyta dig i mattan!", "response": bytes([1, 2, 3, 4]) }, "authData": SAMPLE_AUTH_DATA_MAKE_CREDENTIAL[2:] }))}]")
[
0xa3, 0x63, 0x66, 0x6d, 0x74, 0x71, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2d,
0x73, 0x61, 0x66, 0x65, 0x74, 0x79, 0x6e, 0x65, 0x74, 0x67, 0x61, 0x74, 0x74, 0x53,
0x74, 0x6d, 0x74, 0xa2, 0x63, 0x76, 0x65, 0x72, 0x78, 0x30, 0x4b, 0x6f, 0x6d, 0x20,
0x69, 0x68, 0xc3, 0xa5, 0x67, 0x20, 0x61, 0x74, 0x74, 0x20, 0x64, 0x75, 0x20, 0x61,
0x6c, 0x64, 0x72, 0x69, 0x67, 0x20, 0x66, 0xc3, 0xa5, 0x72, 0x20, 0x73, 0x6e, 0x79,
0x74, 0x61, 0x20, 0x64, 0x69, 0x67, 0x20, 0x69, 0x20, 0x6d, 0x61, 0x74, 0x74, 0x61,
0x6e, 0x21, 0x68, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x01, 0x02,
0x03, 0x04, 0x68, 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x58, 0xa2, 0xc2,
0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84, 0x27,
0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a,
0x87, 0x05, 0x1d, 0xc1, 0x00, 0x00, 0x00, 0x0b, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a,
0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59,
0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f,
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1,
0xb1, 0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe,
0x8b, 0x97, 0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1,
0x22, 0x58, 0x20, 0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b,
0xe5, 0xa6, 0x22, 0x8c, 0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff,
0x73, 0x0f, 0x01, 0x27, 0x8e, 0xa6, 0x1c, 0xa1, 0x6b, 0x68, 0x6d, 0x61, 0x63, 0x2d,
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xf5
]
);
Ok(())
}
#[test]
fn serialize_att_obj_apple() -> Result<(), serde_cbor::Error> {
let att_stmt = AttestationStatement::Apple(serde_cbor::Value::Map(
vec![(
"x5c".to_string().into(),
serde_cbor::Value::Array(vec![
serde_cbor::Value::Bytes(vec![1, 2, 3, 4]),
serde_cbor::Value::Bytes(vec![5, 6, 7, 8]),
]),
)]
.into_iter()
.collect(),
));
assert_eq!(
serde_cbor::ser::to_vec(&att_stmt)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"x5c": [bytes([1, 2, 3, 4]), bytes([5, 6, 7, 8])] }))}]")
[
0xa1, 0x63, 0x78, 0x35, 0x63, 0x82, 0x44, 0x01, 0x02, 0x03, 0x04, 0x44, 0x05, 0x06,
0x07, 0x08
]
);
let att_obj = AttestationObject {
auth_data: serde_cbor::de::from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL)?,
att_stmt,
};
assert_eq!(
serde_cbor::ser::to_vec(&att_obj)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"fmt": "apple", "attStmt": {"x5c": [bytes([1, 2, 3, 4]), bytes([5, 6, 7, 8])] }, "authData": SAMPLE_AUTH_DATA_MAKE_CREDENTIAL[2:] }))}]")
[
0xa3, 0x63, 0x66, 0x6d, 0x74, 0x65, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x67, 0x61, 0x74,
0x74, 0x53, 0x74, 0x6d, 0x74, 0xa1, 0x63, 0x78, 0x35, 0x63, 0x82, 0x44, 0x01, 0x02,
0x03, 0x04, 0x44, 0x05, 0x06, 0x07, 0x08, 0x68, 0x61, 0x75, 0x74, 0x68, 0x44, 0x61,
0x74, 0x61, 0x58, 0xa2, 0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a,
0xb4, 0xe4, 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6,
0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0xc1, 0x00, 0x00, 0x00, 0x0b, 0xf8,
0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc,
0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58,
0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1, 0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b,
0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97, 0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69,
0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, 0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d,
0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c, 0x59, 0x50, 0x1e, 0x4b, 0xcd,
0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e, 0xa6, 0x1c, 0xa1, 0x6b,
0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xf5
]
);
Ok(())
}
#[test]
fn serialize_att_obj_tpm() -> Result<(), serde_cbor::Error> {
let att_stmt = AttestationStatement::Tpm(serde_cbor::Value::Map(
vec![
("ver".to_string().into(), "2.0".to_string().into()),
("alg".to_string().into(), (-7).into()),
(
"x5c".to_string().into(),
serde_cbor::Value::Array(vec![
serde_cbor::Value::Bytes(vec![1, 2, 3, 4]),
serde_cbor::Value::Bytes(vec![5, 6, 7, 8]),
]),
),
(
"sig".to_string().into(),
serde_cbor::Value::Bytes(vec![9, 10, 11, 12]),
),
(
"certInfo".to_string().into(),
serde_cbor::Value::Bytes(vec![13, 14, 15, 16]),
),
(
"pubArea".to_string().into(),
serde_cbor::Value::Bytes(vec![17, 18, 19, 20]),
),
]
.into_iter()
.collect(),
));
assert_eq!(
serde_cbor::ser::to_vec(&att_stmt)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"ver": "2.0", "alg": -7, "x5c": [bytes([1, 2, 3, 4]), bytes([5, 6, 7, 8])], "sig": bytes([9, 10, 11, 12]), "certInfo": bytes([13, 14, 15, 16]), "pubArea": bytes([17, 18, 19, 20]) }))}]")
[
0xa6, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x44, 0x09, 0x0a, 0x0b,
0x0c, 0x63, 0x76, 0x65, 0x72, 0x63, 0x32, 0x2e, 0x30, 0x63, 0x78, 0x35, 0x63, 0x82,
0x44, 0x01, 0x02, 0x03, 0x04, 0x44, 0x05, 0x06, 0x07, 0x08, 0x67, 0x70, 0x75, 0x62,
0x41, 0x72, 0x65, 0x61, 0x44, 0x11, 0x12, 0x13, 0x14, 0x68, 0x63, 0x65, 0x72, 0x74,
0x49, 0x6e, 0x66, 0x6f, 0x44, 0x0d, 0x0e, 0x0f, 0x10
]
);
let att_obj = AttestationObject {
auth_data: serde_cbor::de::from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL)?,
att_stmt,
};
assert_eq!(
serde_cbor::ser::to_vec(&att_obj)?,
// Python: from fido2.cbor import encode; print(f"[{", ".join(f'0x{b:02x}' for b in encode({"fmt": "tpm", "attStmt": {"ver": "2.0", "alg": -7, "x5c": [bytes([1, 2, 3, 4]), bytes([5, 6, 7, 8])], "sig": bytes([9, 10, 11, 12]), "certInfo": bytes([13, 14, 15, 16]), "pubArea": bytes([17, 18, 19, 20]) }, "authData": SAMPLE_AUTH_DATA_MAKE_CREDENTIAL[2:] }))}]")
[
0xa3, 0x63, 0x66, 0x6d, 0x74, 0x63, 0x74, 0x70, 0x6d, 0x67, 0x61, 0x74, 0x74, 0x53,
0x74, 0x6d, 0x74, 0xa6, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x44,
0x09, 0x0a, 0x0b, 0x0c, 0x63, 0x76, 0x65, 0x72, 0x63, 0x32, 0x2e, 0x30, 0x63, 0x78,
0x35, 0x63, 0x82, 0x44, 0x01, 0x02, 0x03, 0x04, 0x44, 0x05, 0x06, 0x07, 0x08, 0x67,
0x70, 0x75, 0x62, 0x41, 0x72, 0x65, 0x61, 0x44, 0x11, 0x12, 0x13, 0x14, 0x68, 0x63,
0x65, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x44, 0x0d, 0x0e, 0x0f, 0x10, 0x68, 0x61,
0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x58, 0xa2, 0xc2, 0x89, 0xc5, 0xca, 0x9b,
0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84, 0x27, 0x43, 0x40, 0x4d, 0x31,
0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a, 0x87, 0x05, 0x1d, 0xc1,
0x00, 0x00, 0x00, 0x0b, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06,
0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x10, 0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c,
0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c, 0x6f, 0xa5, 0x01, 0x02, 0x03,
0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1, 0xc4, 0x58, 0xc5,
0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97, 0xaf, 0xde,
0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20, 0xfa,
0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c,
0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27,
0x8e, 0xa6, 0x1c, 0xa1, 0x6b, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72,
0x65, 0x74, 0xf5
]
);
Ok(())
}
mod hmac_secret {
use std::convert::TryFrom;
use crate::{
crypto::{
COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, Curve, PinUvAuthProtocol,
SharedSecret,
},
ctap2::{attestation::HmacSecretResponse, commands::CommandError},
AuthenticatorInfo,
};
fn make_test_secret(pin_protocol: u64) -> Result<SharedSecret, CommandError> {
let fake_unused_key = COSEKey {
alg: COSEAlgorithm::ECDH_ES_HKDF256,
key: COSEKeyType::EC2(COSEEC2Key {
curve: Curve::SECP256R1,
x: vec![],
y: vec![],
}),
};
let pin_protocol = PinUvAuthProtocol::try_from(&AuthenticatorInfo {
pin_protocols: Some(vec![pin_protocol]),
..Default::default()
})?;
let key = {
let aes_key = 0..32;
let hmac_key = 32..64;
match pin_protocol.id() {
1 => aes_key.collect(),
2 => hmac_key.chain(aes_key).collect(),
_ => unimplemented!(),
}
};
Ok(SharedSecret::new_test(
pin_protocol,
key,
fake_unused_key.clone(),
fake_unused_key,
))
}
#[test]
fn decrypt_confirmed_returns_none() -> Result<(), CommandError> {
let shared_secret = make_test_secret(2)?;
for flag in [true, false] {
let resp = HmacSecretResponse::Confirmed(flag);
let hmac_output = resp.decrypt_secrets(&shared_secret);
assert_eq!(hmac_output, None, "Failed for confirmed flag: {:?}", flag);
}
Ok(())
}
#[cfg(not(feature = "crypto_dummy"))]
mod requires_crypto {
use super::*;
use crate::{
crypto::CryptoError,
ctap2::{attestation::HmacSecretResponse, commands::CommandError},
};
const PIN_PROTOCOL_2_IV: [u8; 16] = [0; 16]; // PIN protocol 1 uses a hard-coded all-zero IV
/// Generated using AES key 0..32 and ciphertext 0..64:
/// ```
/// #!/usr/bin/env python3
/// from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
///
/// key = bytes(range(32))
/// iv = bytes([0] * 16)
/// ciphertext = bytes(range(64))
///
/// cipher = Cipher(algorithms.AES256(key), modes.CBC(iv))
/// decryptor = cipher.decryptor()
/// outputs = list(decryptor.update(ciphertext) + decryptor.finalize())
/// EXPECTED_OUTPUT1 = outputs[0:32]
/// EXPECTED_OUTPUT2 = outputs[32:64]
/// print(EXPECTED_OUTPUT1)
/// print(EXPECTED_OUTPUT2)
/// ```
/// Note: Using WebCrypto to generate these is impractical since they MUST NOT be padded, but WebCrypto inserts PKCS#7 padding.
const EXPECTED_OUTPUT1: [u8; 32] = [
145, 61, 188, 229, 73, 58, 253, 192, 87, 114, 133, 138, 173, 74, 68, 50, 105, 3,
44, 7, 205, 92, 54, 139, 137, 207, 7, 105, 89, 85, 211, 130,
];
/// See [EXPECTED_OUTPUT1] for generation instructions
const EXPECTED_OUTPUT2: Option<[u8; 32]> = Some([
155, 19, 88, 255, 192, 226, 50, 42, 243, 22, 42, 12, 146, 77, 108, 29, 71, 72, 149,
153, 183, 65, 182, 149, 71, 202, 57, 123, 239, 79, 94, 230,
]);
#[test]
fn decrypt_one_secret_pin_protocol_1() -> Result<(), CommandError> {
const CT_LEN: u8 = 32;
let shared_secret = make_test_secret(1)?;
let resp = HmacSecretResponse::Secret((0..CT_LEN).collect());
let hmac_output = resp.decrypt_secrets(&shared_secret).unwrap()?;
assert_eq!(hmac_output.output1, EXPECTED_OUTPUT1, "Incorrect output1");
assert_eq!(hmac_output.output2, None, "Incorrect output2");
Ok(())
}
#[test]
fn decrypt_two_secrets_pin_protocol_1() -> Result<(), CommandError> {
const CT_LEN: u8 = 32 * 2;
let shared_secret = make_test_secret(1)?;
let resp = HmacSecretResponse::Secret((0..CT_LEN).collect());
let hmac_output = resp.decrypt_secrets(&shared_secret).unwrap()?;
assert_eq!(hmac_output.output1, EXPECTED_OUTPUT1, "Incorrect output1");
assert_eq!(hmac_output.output2, EXPECTED_OUTPUT2, "Incorrect output2");
Ok(())
}
#[test]
fn decrypt_one_secret_pin_protocol_2() -> Result<(), CommandError> {
const CT_LEN: u8 = 32;
let shared_secret = make_test_secret(2)?;
let resp = HmacSecretResponse::Secret(
PIN_PROTOCOL_2_IV.iter().copied().chain(0..CT_LEN).collect(),
);
let hmac_output = resp.decrypt_secrets(&shared_secret).unwrap()?;
assert_eq!(hmac_output.output1, EXPECTED_OUTPUT1, "Incorrect output1");
assert_eq!(hmac_output.output2, None, "Incorrect output2");
Ok(())
}
#[test]
fn decrypt_two_secrets_pin_protocol_2() -> Result<(), CommandError> {
const CT_LEN: u8 = 32 * 2;
let shared_secret = make_test_secret(2)?;
let resp = HmacSecretResponse::Secret(
PIN_PROTOCOL_2_IV.iter().copied().chain(0..CT_LEN).collect(),
);
let hmac_output = resp.decrypt_secrets(&shared_secret).unwrap()?;
assert_eq!(hmac_output.output1, EXPECTED_OUTPUT1, "Incorrect output1");
assert_eq!(hmac_output.output2, EXPECTED_OUTPUT2, "Incorrect output2");
Ok(())
}
#[test]
fn decrypt_wrong_length_pin_protocol_2() -> Result<(), CommandError> {
// hmac-secret output can only be multiples of 32 bytes since it operates on whole AES cipher blocks
let shared_secret = make_test_secret(2)?;
{
// Empty cleartext
let resp = HmacSecretResponse::Secret(PIN_PROTOCOL_2_IV.to_vec());
let hmac_output = resp.decrypt_secrets(&shared_secret).unwrap();
assert_eq!(hmac_output, Err(CryptoError::WrongSaltLength));
}
{
// Too long cleartext
let resp = HmacSecretResponse::Secret(
PIN_PROTOCOL_2_IV.iter().copied().chain(0..96).collect(),
);
let hmac_output = resp.decrypt_secrets(&shared_secret).unwrap();
assert_eq!(hmac_output, Err(CryptoError::WrongSaltLength));
}
Ok(())
}
}
}
}