register.rs (181234B)
1 #[cfg(feature = "serde_relaxed")] 2 use self::{ 3 super::ser_relaxed::{RelaxedClientDataJsonParser, SerdeJsonErr}, 4 ser_relaxed::{CustomRegistration, RegistrationRelaxed}, 5 }; 6 #[cfg(all(doc, feature = "bin"))] 7 use super::super::bin::{Decode, Encode}; 8 #[cfg(feature = "bin")] 9 use super::register::bin::{AaguidOwned, MetadataOwned}; 10 use super::{ 11 super::request::register::{FourToSixtyThree, ResidentKeyRequirement}, 12 AuthData, AuthDataContainer, AuthExtOutput, AuthRespErr, AuthResponse, AuthTransports, 13 AuthenticatorAttachment, Backup, CborSuccess, ClientDataJsonParser as _, CollectedClientData, 14 CredentialId, Flag, FromCbor, HmacSecretGet, HmacSecretGetErr, LimitedVerificationParser, 15 ParsedAuthData, Response, SentChallenge, cbor, 16 error::CollectedClientDataErr, 17 register::error::{ 18 AaguidErr, AttestationErr, AttestationObjectErr, AttestedCredentialDataErr, 19 AuthenticatorDataErr, AuthenticatorExtensionOutputErr, CompressedP256PubKeyErr, 20 CompressedP384PubKeyErr, CoseKeyErr, Ed25519PubKeyErr, Ed25519SignatureErr, PubKeyErr, 21 RsaPubKeyErr, UncompressedP256PubKeyErr, UncompressedP384PubKeyErr, 22 }, 23 }; 24 #[cfg(doc)] 25 use super::{ 26 super::{ 27 AuthenticatedCredential, RegisteredCredential, 28 hash::hash_set::FixedCapHashSet, 29 request::{ 30 BackupReq, Challenge, UserVerificationRequirement, 31 auth::{AuthenticationVerificationOptions, PublicKeyCredentialRequestOptions}, 32 register::{CoseAlgorithmIdentifier, Extension, RegistrationServerState}, 33 }, 34 }, 35 AuthenticatorTransport, 36 }; 37 use core::{ 38 cmp::Ordering, 39 convert::Infallible, 40 fmt::{self, Display, Formatter}, 41 }; 42 use ed25519_dalek::{Signature, Verifier as _, VerifyingKey}; 43 use p256::{ 44 AffinePoint as P256Affine, EncodedPoint as P256Pt, NistP256, 45 ecdsa::{DerSignature as P256Sig, VerifyingKey as P256VerKey}, 46 elliptic_curve::{Curve, generic_array::typenum::ToInt as _, point::DecompressPoint as _}, 47 }; 48 use p384::{ 49 AffinePoint as P384Affine, EncodedPoint as P384Pt, NistP384, 50 ecdsa::{DerSignature as P384Sig, VerifyingKey as P384VerKey}, 51 }; 52 use rsa::{ 53 BigUint, RsaPublicKey, 54 pkcs1v15::{self, VerifyingKey as RsaVerKey}, 55 sha2::{Sha256, digest::Digest as _}, 56 }; 57 #[cfg(all(doc, feature = "serde_relaxed"))] 58 use serde::Deserialize; 59 /// Contains functionality to (de)serialize data to a data store. 60 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 61 #[cfg(feature = "bin")] 62 pub mod bin; 63 /// Contains error types. 64 pub mod error; 65 /// Contains functionality to deserialize data from a client. 66 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] 67 #[cfg(feature = "serde")] 68 mod ser; 69 /// Contains functionality to deserialize data from a client in a "relaxed" way. 70 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 71 #[cfg(feature = "serde_relaxed")] 72 pub mod ser_relaxed; 73 /// [`credentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#dom-authenticationextensionsclientinputs-credentialprotectionpolicy). 74 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 75 pub enum CredentialProtectionPolicy { 76 /// `credProtect` was not sent. 77 None, 78 /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional). 79 UserVerificationOptional, 80 /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist). 81 UserVerificationOptionalWithCredentialIdList, 82 /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired). 83 UserVerificationRequired, 84 } 85 impl Display for CredentialProtectionPolicy { 86 #[inline] 87 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 88 f.write_str(match *self { 89 Self::None => "no credential protection policy sent", 90 Self::UserVerificationOptional => "user verification optional", 91 Self::UserVerificationOptionalWithCredentialIdList => { 92 "user verification optional with credential ID list" 93 } 94 Self::UserVerificationRequired => "user verification required", 95 }) 96 } 97 } 98 #[expect(clippy::too_long_first_doc_paragraph, reason = "false positive")] 99 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension) 100 /// and 101 /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension). 102 /// 103 /// Note `hmac-secret-mc` can only exist if `hmac-secret` exists with a value of `true`. 104 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 105 pub enum HmacSecret { 106 /// No `hmac-secret` extension. 107 None, 108 /// `hmac-secret` extension with a value of `false`. 109 NotEnabled, 110 /// `hmac-secret` extension with a value of `true`. 111 Enabled, 112 /// `hmac-secret` extension with a value of `true` and `hmac-secret-mc` contained one encrypted PRF output. 113 One, 114 /// `hmac-secret` extension with a value of `true` and `hmac-secret-mc` contained two encrypted PRF outputs. 115 Two, 116 } 117 /// [Authenticator extension output](https://www.w3.org/TR/webauthn-3/#authenticator-extension-output). 118 #[derive(Clone, Copy, Debug)] 119 pub struct AuthenticatorExtensionOutput { 120 /// [`credProtect`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension). 121 pub cred_protect: CredentialProtectionPolicy, 122 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension) 123 /// and 124 /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension). 125 pub hmac_secret: HmacSecret, 126 /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension). 127 pub min_pin_length: Option<FourToSixtyThree>, 128 } 129 impl AuthExtOutput for AuthenticatorExtensionOutput { 130 fn missing(self) -> bool { 131 matches!(self.cred_protect, CredentialProtectionPolicy::None) 132 && matches!(self.hmac_secret, HmacSecret::None) 133 && self.min_pin_length.is_none() 134 } 135 } 136 /// [`AuthenticatorExtensionOutput`] extensions that are saved in [`StaticState`] because they are used during 137 /// authentication ceremonies. 138 #[derive(Clone, Copy, Debug)] 139 pub struct AuthenticatorExtensionOutputStaticState { 140 /// [`AuthenticatorExtensionOutput::cred_protect`]. 141 pub cred_protect: CredentialProtectionPolicy, 142 /// [`AuthenticatorExtensionOutput::hmac_secret`]. 143 /// 144 /// Note we only care about whether or not it has been enabled. Specifcally this is `None` iff 145 /// [`HmacSecret::None`], `Some(false)` iff [`HmacSecret::NotEnabled`]; otherwise `Some(true)`. 146 pub hmac_secret: Option<bool>, 147 } 148 /// [`AuthenticatorExtensionOutput`] extensions that are saved in [`Metadata`] because they are purely informative 149 /// and not used during authentication ceremonies. 150 #[derive(Clone, Copy, Debug)] 151 pub struct AuthenticatorExtensionOutputMetadata { 152 /// [`AuthenticatorExtensionOutput::min_pin_length`]. 153 pub min_pin_length: Option<FourToSixtyThree>, 154 } 155 impl From<AuthenticatorExtensionOutput> for AuthenticatorExtensionOutputMetadata { 156 #[inline] 157 fn from(value: AuthenticatorExtensionOutput) -> Self { 158 Self { 159 min_pin_length: value.min_pin_length, 160 } 161 } 162 } 163 impl From<AuthenticatorExtensionOutput> for AuthenticatorExtensionOutputStaticState { 164 #[inline] 165 fn from(value: AuthenticatorExtensionOutput) -> Self { 166 Self { 167 cred_protect: value.cred_protect, 168 hmac_secret: match value.hmac_secret { 169 HmacSecret::None => None, 170 HmacSecret::NotEnabled => Some(false), 171 HmacSecret::Enabled | HmacSecret::One | HmacSecret::Two => Some(true), 172 }, 173 } 174 } 175 } 176 impl FromCbor<'_> for CredentialProtectionPolicy { 177 type Err = AuthenticatorExtensionOutputErr; 178 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 179 /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional). 180 const USER_VERIFICATION_OPTIONAL: u8 = cbor::ONE; 181 /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist). 182 const USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST: u8 = cbor::TWO; 183 /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired). 184 const USER_VERIFICATION_REQUIRED: u8 = cbor::THREE; 185 /// `credProtect` key. 186 const KEY: [u8; 12] = [ 187 cbor::TEXT_11, 188 b'c', 189 b'r', 190 b'e', 191 b'd', 192 b'P', 193 b'r', 194 b'o', 195 b't', 196 b'e', 197 b'c', 198 b't', 199 ]; 200 cbor.split_at_checked(KEY.len()).map_or( 201 Ok(CborSuccess { 202 value: Self::None, 203 remaining: cbor, 204 }), 205 |(key, key_rem)| { 206 if key == KEY { 207 key_rem 208 .split_first() 209 .ok_or(AuthenticatorExtensionOutputErr::Len) 210 .and_then(|(uv, remaining)| { 211 match *uv { 212 USER_VERIFICATION_OPTIONAL => Ok(Self::UserVerificationOptional), 213 USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST => { 214 Ok(Self::UserVerificationOptionalWithCredentialIdList) 215 } 216 USER_VERIFICATION_REQUIRED => Ok(Self::UserVerificationRequired), 217 _ => Err(AuthenticatorExtensionOutputErr::CredProtectValue), 218 } 219 .map(|value| CborSuccess { value, remaining }) 220 }) 221 } else { 222 Ok(CborSuccess { 223 value: Self::None, 224 remaining: cbor, 225 }) 226 } 227 }, 228 ) 229 } 230 } 231 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension). 232 enum HmacSecretEnabled { 233 /// No `hmac-secret` extension. 234 None, 235 /// `hmac-secret` set to the contained `bool`. 236 Val(bool), 237 } 238 impl FromCbor<'_> for HmacSecretEnabled { 239 type Err = AuthenticatorExtensionOutputErr; 240 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 241 cbor.split_at_checked(cbor::HMAC_SECRET.len()).map_or( 242 Ok(CborSuccess { 243 value: Self::None, 244 remaining: cbor, 245 }), 246 |(key, key_rem)| { 247 if key == cbor::HMAC_SECRET { 248 key_rem 249 .split_first() 250 .ok_or(AuthenticatorExtensionOutputErr::Len) 251 .and_then(|(hmac, remaining)| { 252 match *hmac { 253 cbor::SIMPLE_FALSE => Ok(Self::Val(false)), 254 cbor::SIMPLE_TRUE => Ok(Self::Val(true)), 255 _ => Err(AuthenticatorExtensionOutputErr::HmacSecretValue), 256 } 257 .map(|value| CborSuccess { value, remaining }) 258 }) 259 } else { 260 Ok(CborSuccess { 261 value: Self::None, 262 remaining: cbor, 263 }) 264 } 265 }, 266 ) 267 } 268 } 269 /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension). 270 enum MinPinLength { 271 /// No `minPinLength` extension. 272 None, 273 /// `minPinLength` with the value of the contained `FourToSixtyThree`. 274 Val(FourToSixtyThree), 275 } 276 impl FromCbor<'_> for MinPinLength { 277 type Err = AuthenticatorExtensionOutputErr; 278 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 279 /// `minPinLength` key. 280 const KEY: [u8; 13] = [ 281 cbor::TEXT_12, 282 b'm', 283 b'i', 284 b'n', 285 b'P', 286 b'i', 287 b'n', 288 b'L', 289 b'e', 290 b'n', 291 b'g', 292 b't', 293 b'h', 294 ]; 295 cbor.split_at_checked(KEY.len()).map_or( 296 Ok(CborSuccess { 297 value: Self::None, 298 remaining: cbor, 299 }), 300 |(key, key_rem)| { 301 if key == KEY { 302 key_rem 303 .split_first() 304 .ok_or(AuthenticatorExtensionOutputErr::Len) 305 .and_then(|(&key_len, remaining)| match key_len.cmp(&24) { 306 Ordering::Less => FourToSixtyThree::from_u8(key_len) 307 .ok_or(AuthenticatorExtensionOutputErr::MinPinLengthValue) 308 .map(|val| CborSuccess { 309 value: Self::Val(val), 310 remaining, 311 }), 312 Ordering::Equal => remaining 313 .split_first() 314 .ok_or(AuthenticatorExtensionOutputErr::Len) 315 .and_then(|(&key_24, rem)| { 316 if key_24 > 23 { 317 FourToSixtyThree::from_u8(key_24) 318 .ok_or( 319 AuthenticatorExtensionOutputErr::MinPinLengthValue, 320 ) 321 .map(|val| CborSuccess { 322 value: Self::Val(val), 323 remaining: rem, 324 }) 325 } else { 326 Err(AuthenticatorExtensionOutputErr::MinPinLengthValue) 327 } 328 }), 329 Ordering::Greater => { 330 Err(AuthenticatorExtensionOutputErr::MinPinLengthValue) 331 } 332 }) 333 } else { 334 Ok(CborSuccess { 335 value: Self::None, 336 remaining: cbor, 337 }) 338 } 339 }, 340 ) 341 } 342 } 343 impl From<HmacSecretGetErr> for AuthenticatorExtensionOutputErr { 344 #[inline] 345 fn from(value: HmacSecretGetErr) -> Self { 346 match value { 347 HmacSecretGetErr::Len => Self::Len, 348 HmacSecretGetErr::Type => Self::HmacSecretMcType, 349 HmacSecretGetErr::Value => Self::HmacSecretMcValue, 350 } 351 } 352 } 353 impl FromCbor<'_> for AuthenticatorExtensionOutput { 354 type Err = AuthenticatorExtensionOutputErr; 355 #[expect( 356 clippy::too_many_lines, 357 reason = "don't want to move logic into outer scope" 358 )] 359 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 360 // We don't allow unsupported extensions; thus the only possibilities is any ordered element of 361 // the power set of {"credProtect":<1, 2, or 3>, "hmac-secret":<true or false>, "minPinLength":<0-255>, "hmac-secret-mc":<48|80 bytes>}. 362 // Since the keys are the same type (text), order is first done based on length; and then 363 // byte-wise lexical order is followed; thus `credProtect` must come before `hmac-secret` which 364 // must come before `minPinLength` which comes before `hmac-secret-mc`. 365 // 366 // Note `hmac-secret-mc` can only exist if `hmac-secret` exists with a value of `true`. 367 cbor.split_first().map_or_else( 368 || { 369 Ok(CborSuccess { 370 value: Self { 371 cred_protect: CredentialProtectionPolicy::None, 372 hmac_secret: HmacSecret::None, 373 min_pin_length: None, 374 }, 375 remaining: cbor, 376 }) 377 }, 378 |(map, map_rem)| match *map { 379 cbor::MAP_1 => { 380 CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| { 381 if matches!(cred_success.value, CredentialProtectionPolicy::None) { 382 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then( 383 |hmac_success| match hmac_success.value { 384 HmacSecretEnabled::None => MinPinLength::from_cbor( 385 hmac_success.remaining, 386 ) 387 .and_then(|pin_success| match pin_success.value { 388 MinPinLength::None => { 389 // We don't even bother checking for `HmacSecretGet` since 390 // it's only valid when `HmacSecretEnabled` exists with a value 391 // of `true`. 392 Err(AuthenticatorExtensionOutputErr::Missing) 393 } 394 MinPinLength::Val(min_pin_len) => Ok(CborSuccess { 395 value: Self { 396 cred_protect: cred_success.value, 397 hmac_secret: HmacSecret::None, 398 min_pin_length: Some(min_pin_len), 399 }, 400 remaining: pin_success.remaining, 401 }), 402 }), 403 HmacSecretEnabled::Val(hmac) => Ok(CborSuccess { 404 value: Self { 405 cred_protect: cred_success.value, 406 hmac_secret: if hmac { 407 HmacSecret::Enabled 408 } else { 409 HmacSecret::NotEnabled 410 }, 411 min_pin_length: None, 412 }, 413 remaining: hmac_success.remaining, 414 }), 415 }, 416 ) 417 } else { 418 Ok(CborSuccess { 419 value: Self { 420 cred_protect: cred_success.value, 421 hmac_secret: HmacSecret::None, 422 min_pin_length: None, 423 }, 424 remaining: cred_success.remaining, 425 }) 426 } 427 }) 428 } 429 cbor::MAP_2 => { 430 CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| { 431 if matches!(cred_success.value, CredentialProtectionPolicy::None) { 432 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then( 433 |hmac_success| match hmac_success.value { 434 HmacSecretEnabled::None => { 435 // We don't even bother checking for `HmacSecretGet` since 436 // it's only valid when `HmacSecretEnabled` exists with a value 437 // of `true`. 438 Err(AuthenticatorExtensionOutputErr::Missing) 439 } 440 HmacSecretEnabled::Val(hmac) => MinPinLength::from_cbor( 441 hmac_success.remaining, 442 ) 443 .and_then(|pin_success| match pin_success.value { 444 MinPinLength::None => { 445 if hmac { 446 HmacSecretGet::<true>::from_cbor( 447 pin_success.remaining, 448 ) 449 .map_err(AuthenticatorExtensionOutputErr::from) 450 .and_then(|hmac_get| match hmac_get.value { 451 HmacSecretGet::None => Err( 452 AuthenticatorExtensionOutputErr::Missing, 453 ), 454 HmacSecretGet::One => Ok(CborSuccess { 455 value: Self { 456 cred_protect: cred_success.value, 457 hmac_secret: HmacSecret::One, 458 min_pin_length: None, 459 }, 460 remaining: hmac_get.remaining, 461 }), 462 HmacSecretGet::Two => Ok(CborSuccess { 463 value: Self { 464 cred_protect: cred_success.value, 465 hmac_secret: HmacSecret::Two, 466 min_pin_length: None, 467 }, 468 remaining: hmac_get.remaining, 469 }), 470 }) 471 } else { 472 // We don't even bother checking for `HmacSecretGet` since 473 // it's only valid when `HmacSecretEnabled` exists with a value 474 // of `true`. 475 Err(AuthenticatorExtensionOutputErr::Missing) 476 } 477 } 478 MinPinLength::Val(min_pin_len) => Ok(CborSuccess { 479 value: Self { 480 cred_protect: cred_success.value, 481 hmac_secret: if hmac { 482 HmacSecret::Enabled 483 } else { 484 HmacSecret::NotEnabled 485 }, 486 min_pin_length: Some(min_pin_len), 487 }, 488 remaining: pin_success.remaining, 489 }), 490 }), 491 }, 492 ) 493 } else { 494 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then( 495 |hmac_success| match hmac_success.value { 496 HmacSecretEnabled::None => MinPinLength::from_cbor( 497 hmac_success.remaining, 498 ) 499 .and_then(|pin_success| match pin_success.value { 500 MinPinLength::None => { 501 // We don't even bother checking for `HmacSecretGet` since 502 // it's only valid when `HmacSecretEnabled` exists with a value 503 // of `true`. 504 Err(AuthenticatorExtensionOutputErr::Missing) 505 } 506 MinPinLength::Val(min_pin_len) => Ok(CborSuccess { 507 value: Self { 508 cred_protect: cred_success.value, 509 hmac_secret: HmacSecret::None, 510 min_pin_length: Some(min_pin_len), 511 }, 512 remaining: pin_success.remaining, 513 }), 514 }), 515 // We don't even bother checking for `HmacSecretGet` since 516 // it's only valid when `HmacSecretEnabled` exists with a value 517 // of `true`. 518 HmacSecretEnabled::Val(hmac) => Ok(CborSuccess { 519 value: Self { 520 cred_protect: cred_success.value, 521 hmac_secret: if hmac { 522 HmacSecret::Enabled 523 } else { 524 HmacSecret::NotEnabled 525 }, 526 min_pin_length: None, 527 }, 528 remaining: hmac_success.remaining, 529 }), 530 }, 531 ) 532 } 533 }) 534 } 535 cbor::MAP_3 => { 536 CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| { 537 if matches!(cred_success.value, CredentialProtectionPolicy::None) { 538 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then( 539 |hmac_success| match hmac_success.value { 540 HmacSecretEnabled::None => { 541 Err(AuthenticatorExtensionOutputErr::Missing) 542 } 543 HmacSecretEnabled::Val(hmac) => { 544 if hmac { 545 MinPinLength::from_cbor( 546 hmac_success.remaining, 547 ) 548 .and_then(|pin_success| match pin_success.value { 549 MinPinLength::None => Err(AuthenticatorExtensionOutputErr::Missing), 550 MinPinLength::Val(min_pin_len) => HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| { 551 match hmac_get.value { 552 HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing), 553 HmacSecretGet::One => { 554 Ok(CborSuccess { 555 value: Self { 556 cred_protect: cred_success.value, 557 hmac_secret: HmacSecret::One, 558 min_pin_length: Some(min_pin_len), 559 }, 560 remaining: hmac_get.remaining, 561 }) 562 } 563 HmacSecretGet::Two => { 564 Ok(CborSuccess { 565 value: Self { 566 cred_protect: cred_success.value, 567 hmac_secret: HmacSecret::Two, 568 min_pin_length: Some(min_pin_len), 569 }, 570 remaining: hmac_get.remaining, 571 }) 572 } 573 } 574 }) 575 }) 576 } else { 577 // We don't even bother checking for `HmacSecretGet` since 578 // it's only valid when `HmacSecretEnabled` exists with a value 579 // of `true`. 580 Err(AuthenticatorExtensionOutputErr::Missing) 581 } 582 } 583 }, 584 ) 585 } else { 586 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then( 587 |hmac_success| match hmac_success.value { 588 // We don't even bother checking for `HmacSecretGet` since 589 // it's only valid when `HmacSecretEnabled` exists with a value 590 // of `true`. 591 HmacSecretEnabled::None => Err(AuthenticatorExtensionOutputErr::Missing), 592 HmacSecretEnabled::Val(hmac) => { 593 MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| { 594 match pin_success.value { 595 MinPinLength::None => { 596 if hmac { 597 HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| { 598 match hmac_get.value { 599 HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing), 600 HmacSecretGet::One => { 601 Ok(CborSuccess { 602 value: Self { 603 cred_protect: cred_success.value, 604 hmac_secret: HmacSecret::One, 605 min_pin_length: None, 606 }, 607 remaining: hmac_get.remaining, 608 }) 609 } 610 HmacSecretGet::Two => { 611 Ok(CborSuccess { 612 value: Self { 613 cred_protect: cred_success.value, 614 hmac_secret: HmacSecret::Two, 615 min_pin_length: None, 616 }, 617 remaining: hmac_get.remaining, 618 }) 619 } 620 } 621 }) 622 } else { 623 // We don't even bother checking for `HmacSecretGet` since 624 // it's only valid when `HmacSecretEnabled` exists with a value 625 // of `true`. 626 Err(AuthenticatorExtensionOutputErr::Missing) 627 } 628 } 629 MinPinLength::Val(min_pin_len) => { 630 Ok(CborSuccess { 631 value: Self { 632 cred_protect: cred_success.value, 633 hmac_secret: if hmac { HmacSecret::Enabled } else { HmacSecret::NotEnabled }, 634 min_pin_length: Some(min_pin_len), 635 }, 636 remaining: pin_success.remaining, 637 }) 638 } 639 } 640 }) 641 } 642 } 643 ) 644 } 645 }) 646 } 647 cbor::MAP_4 => { 648 CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| { 649 if matches!(cred_success.value, CredentialProtectionPolicy::None) { 650 Err(AuthenticatorExtensionOutputErr::Missing) 651 } else { 652 HmacSecretEnabled::from_cbor(cred_success.remaining).and_then( 653 |hmac_success| match hmac_success.value { 654 HmacSecretEnabled::None => { 655 Err(AuthenticatorExtensionOutputErr::Missing) 656 } 657 HmacSecretEnabled::Val(hmac) => { 658 if hmac { 659 MinPinLength::from_cbor( 660 hmac_success.remaining, 661 ) 662 .and_then(|pin_success| match pin_success.value { 663 MinPinLength::None => { 664 Err(AuthenticatorExtensionOutputErr::Missing) 665 } 666 MinPinLength::Val(min_pin_len) => HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| { 667 match hmac_get.value { 668 HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing), 669 HmacSecretGet::One => { 670 Ok(CborSuccess { 671 value: Self { 672 cred_protect: cred_success.value, 673 hmac_secret: HmacSecret::One, 674 min_pin_length: Some(min_pin_len), 675 }, 676 remaining: hmac_get.remaining, 677 }) 678 } 679 HmacSecretGet::Two => { 680 Ok(CborSuccess { 681 value: Self { 682 cred_protect: cred_success.value, 683 hmac_secret: HmacSecret::Two, 684 min_pin_length: Some(min_pin_len), 685 }, 686 remaining: hmac_get.remaining, 687 }) 688 } 689 } 690 }) 691 }) 692 } else { 693 // We don't even bother checking for `HmacSecretGet` since 694 // it's only valid when `HmacSecretEnabled` exists with a value 695 // of `true`. 696 Err(AuthenticatorExtensionOutputErr::Missing) 697 } 698 } 699 }, 700 ) 701 } 702 }) 703 } 704 _ => Err(AuthenticatorExtensionOutputErr::CborHeader), 705 }, 706 ) 707 } 708 } 709 /// 32-bytes representing an alleged Ed25519 public key (i.e., compressed y-coordinate). 710 #[derive(Clone, Copy, Debug)] 711 pub struct Ed25519PubKey<T>(T); 712 impl<T> Ed25519PubKey<T> { 713 /// Returns the contained data consuming `self`. 714 #[inline] 715 pub fn into_inner(self) -> T { 716 self.0 717 } 718 /// Returns the contained data. 719 #[inline] 720 pub const fn inner(&self) -> &T { 721 &self.0 722 } 723 } 724 impl<T: AsRef<[u8]>> Ed25519PubKey<T> { 725 /// Returns the compressed y-coordinate. 726 #[inline] 727 #[must_use] 728 pub fn compressed_y_coordinate(&self) -> &[u8] { 729 self.0.as_ref() 730 } 731 } 732 impl Ed25519PubKey<&[u8]> { 733 /// Validates `self` is in fact a valid Ed25519 public key. 734 /// 735 /// # Errors 736 /// 737 /// Errors iff `self` is not a valid Ed25519 public key. 738 #[inline] 739 pub fn validate(self) -> Result<(), PubKeyErr> { 740 self.into_ver_key().map(|_| ()) 741 } 742 /// Converts `self` into [`VerifyingKey`]. 743 pub(super) fn into_ver_key(self) -> Result<VerifyingKey, PubKeyErr> { 744 self.into_owned().into_ver_key() 745 } 746 /// Transforms `self` into an "owned" version. 747 #[inline] 748 #[must_use] 749 pub fn into_owned(self) -> Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> { 750 Ed25519PubKey(*Ed25519PubKey::<&[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::from(self).0) 751 } 752 } 753 impl Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> { 754 /// Validates `self` is in fact a valid Ed25519 public key. 755 /// 756 /// # Errors 757 /// 758 /// Errors iff `self` is not a valid Ed25519 public key. 759 #[inline] 760 pub fn validate(self) -> Result<(), PubKeyErr> { 761 self.into_ver_key().map(|_| ()) 762 } 763 /// Converts `self` into [`VerifyingKey`]. 764 fn into_ver_key(self) -> Result<VerifyingKey, PubKeyErr> { 765 // ["Taming the many EdDSAs"](https://eprint.iacr.org/2020/1244.pdf) goes over 766 // and proves varying levels of signature security. The only property that is 767 // important for WebAuthn is existential unforgeability under chosen message 768 // attacks (EUF-CMA). No matter how `ed25519-dalek` is used this is met. 769 // Additional properties that may be of importance are strong unforgeability 770 // under chosen message attacks (SUF-CMA), binding signature (BS), and 771 // strongly binding signature (SBS). 772 // No matter how `ed25519-dalek` is used, SUF-CMA is achieved. Because 773 // we always achieve SUF-CMA, we elect—despite no benefit in WebAuthn—to also 774 // achieve SBS. One can achieve SBS-secure by simply rejecting small-order 775 // keys which is precisely what `VerifyingKey::is_weak` does. 776 // Note this means we _don't_ conform to [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032) 777 // nor [NIST SP 800-186](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf). 778 // As stated, there is no additional security by conforming to the above specs though; 779 // furthermore, RFC 8032 does not require rejecting small-order points despite requiring 780 // canonical encoding of points; thus it does not achieve SBS-security. 781 // NIST SP 800-186 does achieve SUF-CMA and SBS-security but requires additional properties 782 // that have no cryptographic importance. Specifically it mandates canonicity of encodings 783 // for both public keys and signatures and requires not only that points not be small-order 784 // but more strictly that points are in the prime-order subgroup (excluding the identity). 785 // This is more work for no benefit, so we elect to stay within the confines of the exposed API. 786 VerifyingKey::from_bytes(&self.0) 787 .map_err(|_e| PubKeyErr::Ed25519) 788 .and_then(|key| { 789 if key.is_weak() { 790 Err(PubKeyErr::Ed25519) 791 } else { 792 Ok(key) 793 } 794 }) 795 } 796 } 797 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Ed25519PubKey<&'b [u8]> { 798 type Error = Ed25519PubKeyErr; 799 /// Interprets `value` as the compressed y-coordinate of an Ed25519 public key. 800 #[inline] 801 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { 802 if value.len() == ed25519_dalek::PUBLIC_KEY_LENGTH { 803 Ok(Self(value)) 804 } else { 805 Err(Ed25519PubKeyErr) 806 } 807 } 808 } 809 impl From<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> 810 for Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> 811 { 812 #[inline] 813 fn from(value: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]) -> Self { 814 Self(value) 815 } 816 } 817 impl<'a: 'b, 'b> From<&'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>> 818 for Ed25519PubKey<&'b [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> 819 { 820 #[inline] 821 fn from(value: &'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self { 822 Self(&value.0) 823 } 824 } 825 impl<'a: 'b, 'b> From<Ed25519PubKey<&'a [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>> 826 for Ed25519PubKey<&'b [u8]> 827 { 828 #[inline] 829 fn from(value: Ed25519PubKey<&'a [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self { 830 Self(value.0.as_slice()) 831 } 832 } 833 impl<'a: 'b, 'b> From<&'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>> 834 for Ed25519PubKey<&'b [u8]> 835 { 836 #[inline] 837 fn from(value: &'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self { 838 Self(value.0.as_slice()) 839 } 840 } 841 impl<'a: 'b, 'b> From<Ed25519PubKey<&'a [u8]>> 842 for Ed25519PubKey<&'b [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> 843 { 844 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 845 #[inline] 846 fn from(value: Ed25519PubKey<&'a [u8]>) -> Self { 847 Self( 848 value 849 .0 850 .try_into() 851 .unwrap_or_else(|_e| unreachable!("there is a bug in &Array::try_from")), 852 ) 853 } 854 } 855 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<Ed25519PubKey<T>> for Ed25519PubKey<T2> { 856 #[inline] 857 fn eq(&self, other: &Ed25519PubKey<T>) -> bool { 858 self.0 == other.0 859 } 860 } 861 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<Ed25519PubKey<T>> for &Ed25519PubKey<T2> { 862 #[inline] 863 fn eq(&self, other: &Ed25519PubKey<T>) -> bool { 864 **self == *other 865 } 866 } 867 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&Ed25519PubKey<T>> for Ed25519PubKey<T2> { 868 #[inline] 869 fn eq(&self, other: &&Ed25519PubKey<T>) -> bool { 870 *self == **other 871 } 872 } 873 impl<T: Eq> Eq for Ed25519PubKey<T> {} 874 /// Two 32-byte regions representing the big-endian x and y coordinates of an alleged P-256 public key. 875 #[derive(Clone, Copy, Debug)] 876 pub struct UncompressedP256PubKey<'a>(&'a [u8], &'a [u8]); 877 impl<'a> UncompressedP256PubKey<'a> { 878 /// Returns the big-endian x-coordinate. 879 #[inline] 880 #[must_use] 881 pub const fn x(self) -> &'a [u8] { 882 self.0 883 } 884 /// Returns the big-endian y-coordinate. 885 #[inline] 886 #[must_use] 887 pub const fn y(self) -> &'a [u8] { 888 self.1 889 } 890 /// Validates `self` is in fact a valid P-256 public key. 891 /// 892 /// # Errors 893 /// 894 /// Errors iff `self` is not a valid P-256 public key. 895 #[inline] 896 pub fn validate(self) -> Result<(), PubKeyErr> { 897 self.into_ver_key().map(|_| ()) 898 } 899 /// Converts `self` into [`P256VerKey`]. 900 fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> { 901 P256VerKey::from_encoded_point(&P256Pt::from_affine_coordinates( 902 self.0.into(), 903 self.1.into(), 904 false, 905 )) 906 .map_err(|_e| PubKeyErr::P256) 907 } 908 /// Returns `true` iff [`Self::y`] is odd. 909 #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")] 910 #[inline] 911 #[must_use] 912 pub const fn y_is_odd(self) -> bool { 913 // `self.1.len() == 32`, so this won't `panic`. 914 self.1[31] & 1 == 1 915 } 916 /// Transforms `self` into the compressed version that owns the data. 917 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 918 #[inline] 919 #[must_use] 920 pub fn into_compressed( 921 self, 922 ) -> CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]> { 923 CompressedP256PubKey { 924 x: self.0.try_into().unwrap_or_else(|_e| unreachable!("there is a bug in UncompressedP256PubKey that allows for the x-coordinate to not be 32 bytes in length")), 925 y_is_odd: self.y_is_odd(), 926 } 927 } 928 } 929 impl<'a: 'b, 'b> TryFrom<(&'a [u8], &'a [u8])> for UncompressedP256PubKey<'b> { 930 type Error = UncompressedP256PubKeyErr; 931 /// The first item is the big-endian x-coordinate, and the second item is the big-endian y-coordinate. 932 #[inline] 933 fn try_from((x, y): (&'a [u8], &'a [u8])) -> Result<Self, Self::Error> { 934 /// Number of bytes each coordinate is made of. 935 const COORD_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; 936 if x.len() == COORD_LEN { 937 if y.len() == COORD_LEN { 938 Ok(Self(x, y)) 939 } else { 940 Err(UncompressedP256PubKeyErr::Y) 941 } 942 } else { 943 Err(UncompressedP256PubKeyErr::X) 944 } 945 } 946 } 947 impl PartialEq<UncompressedP256PubKey<'_>> for UncompressedP256PubKey<'_> { 948 #[inline] 949 fn eq(&self, other: &UncompressedP256PubKey<'_>) -> bool { 950 self.0 == other.0 && self.1 == other.1 951 } 952 } 953 impl PartialEq<UncompressedP256PubKey<'_>> for &UncompressedP256PubKey<'_> { 954 #[inline] 955 fn eq(&self, other: &UncompressedP256PubKey<'_>) -> bool { 956 **self == *other 957 } 958 } 959 impl PartialEq<&UncompressedP256PubKey<'_>> for UncompressedP256PubKey<'_> { 960 #[inline] 961 fn eq(&self, other: &&UncompressedP256PubKey<'_>) -> bool { 962 *self == **other 963 } 964 } 965 impl Eq for UncompressedP256PubKey<'_> {} 966 /// 32-bytes representing the big-endian x-coordinate and a `bool` representing whether the y-coordinate 967 /// is odd of an alleged P-256 public key. 968 #[derive(Clone, Copy, Debug)] 969 pub struct CompressedP256PubKey<T> { 970 /// 32-byte x-coordinate. 971 x: T, 972 /// `true` iff the y-coordinate is odd. 973 y_is_odd: bool, 974 } 975 impl<T> CompressedP256PubKey<T> { 976 /// Returns [`Self::x`] and [`Self::y_is_odd`] consuming `self`. 977 #[inline] 978 pub fn into_parts(self) -> (T, bool) { 979 (self.x, self.y_is_odd) 980 } 981 /// Returns [`Self::x`] and [`Self::y_is_odd`]. 982 #[inline] 983 pub const fn as_parts(&self) -> (&T, bool) { 984 (&self.x, self.y_is_odd) 985 } 986 /// Returns the 32-byte big-endian x-coordinate. 987 #[inline] 988 pub const fn x(&self) -> &T { 989 &self.x 990 } 991 /// `true` iff the y-coordinate is odd. 992 #[inline] 993 pub const fn y_is_odd(&self) -> bool { 994 self.y_is_odd 995 } 996 } 997 impl CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]> { 998 /// Validates `self` is in fact a valid P-256 public key. 999 /// 1000 /// # Errors 1001 /// 1002 /// Errors iff `self` is not a valid P-256 public key. 1003 #[inline] 1004 pub fn validate(self) -> Result<(), PubKeyErr> { 1005 self.into_ver_key().map(|_| ()) 1006 } 1007 /// Converts `self` into [`P256VerKey`]. 1008 pub(super) fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> { 1009 P256Affine::decompress(&self.x.into(), u8::from(self.y_is_odd).into()) 1010 .into_option() 1011 .ok_or(PubKeyErr::P256) 1012 .and_then(|pt| P256VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P256)) 1013 } 1014 } 1015 impl CompressedP256PubKey<&[u8]> { 1016 /// Validates `self` is in fact a valid P-256 public key. 1017 /// 1018 /// # Errors 1019 /// 1020 /// Errors iff `self` is not a valid P-256 public key. 1021 #[inline] 1022 pub fn validate(self) -> Result<(), PubKeyErr> { 1023 self.into_ver_key().map(|_| ()) 1024 } 1025 /// Converts `self` into [`P256VerKey`]. 1026 pub(super) fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> { 1027 P256Affine::decompress(self.x.into(), u8::from(self.y_is_odd).into()) 1028 .into_option() 1029 .ok_or(PubKeyErr::P256) 1030 .and_then(|pt| P256VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P256)) 1031 } 1032 } 1033 impl<'a: 'b, 'b> TryFrom<(&'a [u8], bool)> for CompressedP256PubKey<&'b [u8]> { 1034 type Error = CompressedP256PubKeyErr; 1035 #[inline] 1036 fn try_from((x, y_is_odd): (&'a [u8], bool)) -> Result<Self, Self::Error> { 1037 /// The number of bytes the x-coordinate is. 1038 const X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; 1039 if x.len() == X_LEN { 1040 Ok(Self { x, y_is_odd }) 1041 } else { 1042 Err(CompressedP256PubKeyErr) 1043 } 1044 } 1045 } 1046 impl From<([u8; <NistP256 as Curve>::FieldBytesSize::INT], bool)> 1047 for CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]> 1048 { 1049 #[inline] 1050 fn from((x, y_is_odd): ([u8; <NistP256 as Curve>::FieldBytesSize::INT], bool)) -> Self { 1051 Self { x, y_is_odd } 1052 } 1053 } 1054 impl<'a: 'b, 'b> From<&'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>> 1055 for CompressedP256PubKey<&'b [u8; <NistP256 as Curve>::FieldBytesSize::INT]> 1056 { 1057 #[inline] 1058 fn from( 1059 value: &'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>, 1060 ) -> Self { 1061 Self { 1062 x: &value.x, 1063 y_is_odd: value.y_is_odd, 1064 } 1065 } 1066 } 1067 impl<'a: 'b, 'b> From<CompressedP256PubKey<&'a [u8; <NistP256 as Curve>::FieldBytesSize::INT]>> 1068 for CompressedP256PubKey<&'b [u8]> 1069 { 1070 #[inline] 1071 fn from( 1072 value: CompressedP256PubKey<&'a [u8; <NistP256 as Curve>::FieldBytesSize::INT]>, 1073 ) -> Self { 1074 Self { 1075 x: value.x.as_slice(), 1076 y_is_odd: value.y_is_odd, 1077 } 1078 } 1079 } 1080 impl<'a: 'b, 'b> From<&'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>> 1081 for CompressedP256PubKey<&'b [u8]> 1082 { 1083 #[inline] 1084 fn from( 1085 value: &'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>, 1086 ) -> Self { 1087 Self { 1088 x: value.x.as_slice(), 1089 y_is_odd: value.y_is_odd, 1090 } 1091 } 1092 } 1093 impl<'a: 'b, 'b> From<CompressedP256PubKey<&'a [u8]>> 1094 for CompressedP256PubKey<&'b [u8; <NistP256 as Curve>::FieldBytesSize::INT]> 1095 { 1096 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 1097 #[inline] 1098 fn from(value: CompressedP256PubKey<&'a [u8]>) -> Self { 1099 Self { 1100 x: value 1101 .x 1102 .try_into() 1103 .unwrap_or_else(|_e| unreachable!("&Array::try_from has a bug")), 1104 y_is_odd: value.y_is_odd, 1105 } 1106 } 1107 } 1108 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP256PubKey<T>> 1109 for CompressedP256PubKey<T2> 1110 { 1111 #[inline] 1112 fn eq(&self, other: &CompressedP256PubKey<T>) -> bool { 1113 self.x == other.x && self.y_is_odd == other.y_is_odd 1114 } 1115 } 1116 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP256PubKey<T>> 1117 for &CompressedP256PubKey<T2> 1118 { 1119 #[inline] 1120 fn eq(&self, other: &CompressedP256PubKey<T>) -> bool { 1121 **self == *other 1122 } 1123 } 1124 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&CompressedP256PubKey<T>> 1125 for CompressedP256PubKey<T2> 1126 { 1127 #[inline] 1128 fn eq(&self, other: &&CompressedP256PubKey<T>) -> bool { 1129 *self == **other 1130 } 1131 } 1132 impl<T: Eq> Eq for CompressedP256PubKey<T> {} 1133 /// Two 48-byte regions representing the big-endian x and y coordinates of an alleged P-384 public key. 1134 #[derive(Clone, Copy, Debug)] 1135 pub struct UncompressedP384PubKey<'a>(&'a [u8], &'a [u8]); 1136 impl<'a> UncompressedP384PubKey<'a> { 1137 /// Returns the big-endian x-coordinate. 1138 #[inline] 1139 #[must_use] 1140 pub const fn x(self) -> &'a [u8] { 1141 self.0 1142 } 1143 /// Returns the big-endian y-coordinate. 1144 #[inline] 1145 #[must_use] 1146 pub const fn y(self) -> &'a [u8] { 1147 self.1 1148 } 1149 /// Validates `self` is in fact a valid P-384 public key. 1150 /// 1151 /// # Errors 1152 /// 1153 /// Errors iff `self` is not a valid P-384 public key. 1154 #[inline] 1155 pub fn validate(self) -> Result<(), PubKeyErr> { 1156 self.into_ver_key().map(|_| ()) 1157 } 1158 /// Converts `self` into [`P384VerKey`]. 1159 fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> { 1160 P384VerKey::from_encoded_point(&P384Pt::from_affine_coordinates( 1161 self.0.into(), 1162 self.1.into(), 1163 false, 1164 )) 1165 .map_err(|_e| PubKeyErr::P384) 1166 } 1167 /// Returns `true` iff [`Self::y`] is odd. 1168 #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")] 1169 #[inline] 1170 #[must_use] 1171 pub const fn y_is_odd(self) -> bool { 1172 // `self.1.len() == 48`, so this won't `panic`. 1173 self.1[47] & 1 == 1 1174 } 1175 /// Transforms `self` into the compressed version that owns the data. 1176 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 1177 #[inline] 1178 #[must_use] 1179 pub fn into_compressed( 1180 self, 1181 ) -> CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]> { 1182 CompressedP384PubKey { 1183 x: self.0.try_into().unwrap_or_else(|_e| unreachable!("there is a bug in UncompressedP384PubKey that allows for the x-coordinate to not be 48 bytes in length")), 1184 y_is_odd: self.y_is_odd(), 1185 } 1186 } 1187 } 1188 impl<'a: 'b, 'b> TryFrom<(&'a [u8], &'a [u8])> for UncompressedP384PubKey<'b> { 1189 type Error = UncompressedP384PubKeyErr; 1190 /// The first item is the big-endian x-coordinate, and the second item is the big-endian y-coordinate. 1191 #[inline] 1192 fn try_from((x, y): (&'a [u8], &'a [u8])) -> Result<Self, Self::Error> { 1193 /// Number of bytes each coordinate is made of. 1194 const COORD_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; 1195 if x.len() == COORD_LEN { 1196 if y.len() == COORD_LEN { 1197 Ok(Self(x, y)) 1198 } else { 1199 Err(UncompressedP384PubKeyErr::Y) 1200 } 1201 } else { 1202 Err(UncompressedP384PubKeyErr::X) 1203 } 1204 } 1205 } 1206 impl PartialEq<UncompressedP384PubKey<'_>> for UncompressedP384PubKey<'_> { 1207 #[inline] 1208 fn eq(&self, other: &UncompressedP384PubKey<'_>) -> bool { 1209 self.0 == other.0 && self.1 == other.1 1210 } 1211 } 1212 impl PartialEq<UncompressedP384PubKey<'_>> for &UncompressedP384PubKey<'_> { 1213 #[inline] 1214 fn eq(&self, other: &UncompressedP384PubKey<'_>) -> bool { 1215 **self == *other 1216 } 1217 } 1218 impl PartialEq<&UncompressedP384PubKey<'_>> for UncompressedP384PubKey<'_> { 1219 #[inline] 1220 fn eq(&self, other: &&UncompressedP384PubKey<'_>) -> bool { 1221 *self == **other 1222 } 1223 } 1224 impl Eq for UncompressedP384PubKey<'_> {} 1225 /// 48-bytes representing the big-endian x-coordinate and a `bool` representing whether the y-coordinate 1226 /// is odd of an alleged P-384 public key. 1227 #[derive(Clone, Copy, Debug)] 1228 pub struct CompressedP384PubKey<T> { 1229 /// 48-byte x-coordinate. 1230 x: T, 1231 /// `true` iff the y-coordinate is odd. 1232 y_is_odd: bool, 1233 } 1234 impl<T> CompressedP384PubKey<T> { 1235 /// Returns [`Self::x`] and [`Self::y_is_odd`] consuming `self`. 1236 #[inline] 1237 pub fn into_parts(self) -> (T, bool) { 1238 (self.x, self.y_is_odd) 1239 } 1240 /// Returns [`Self::x`] and [`Self::y_is_odd`]. 1241 #[inline] 1242 pub const fn as_parts(&self) -> (&T, bool) { 1243 (&self.x, self.y_is_odd) 1244 } 1245 /// Returns the 48-byte big-endian x-coordinate. 1246 #[inline] 1247 pub const fn x(&self) -> &T { 1248 &self.x 1249 } 1250 /// `true` iff the y-coordinate is odd. 1251 #[inline] 1252 #[must_use] 1253 pub const fn y_is_odd(&self) -> bool { 1254 self.y_is_odd 1255 } 1256 } 1257 impl CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]> { 1258 /// Validates `self` is in fact a valid P-384 public key. 1259 /// 1260 /// # Errors 1261 /// 1262 /// Errors iff `self` is not a valid P-384 public key. 1263 #[inline] 1264 pub fn validate(self) -> Result<(), PubKeyErr> { 1265 self.into_ver_key().map(|_| ()) 1266 } 1267 /// Converts `self` into [`P384VerKey`]. 1268 pub(super) fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> { 1269 P384Affine::decompress(&self.x.into(), u8::from(self.y_is_odd).into()) 1270 .into_option() 1271 .ok_or(PubKeyErr::P384) 1272 .and_then(|pt| P384VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P384)) 1273 } 1274 } 1275 impl CompressedP384PubKey<&[u8]> { 1276 /// Validates `self` is in fact a valid P-384 public key. 1277 /// 1278 /// # Errors 1279 /// 1280 /// Errors iff `self` is not a valid P-384 public key. 1281 #[inline] 1282 pub fn validate(self) -> Result<(), PubKeyErr> { 1283 self.into_ver_key().map(|_| ()) 1284 } 1285 /// Converts `self` into [`P384VerKey`]. 1286 pub(super) fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> { 1287 P384Affine::decompress(self.x.into(), u8::from(self.y_is_odd).into()) 1288 .into_option() 1289 .ok_or(PubKeyErr::P384) 1290 .and_then(|pt| P384VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P384)) 1291 } 1292 } 1293 impl<'a: 'b, 'b> TryFrom<(&'a [u8], bool)> for CompressedP384PubKey<&'b [u8]> { 1294 type Error = CompressedP384PubKeyErr; 1295 #[inline] 1296 fn try_from((x, y_is_odd): (&'a [u8], bool)) -> Result<Self, Self::Error> { 1297 /// Number of bytes of the x-coordinate is. 1298 const X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; 1299 if x.len() == X_LEN { 1300 Ok(Self { x, y_is_odd }) 1301 } else { 1302 Err(CompressedP384PubKeyErr) 1303 } 1304 } 1305 } 1306 impl From<([u8; <NistP384 as Curve>::FieldBytesSize::INT], bool)> 1307 for CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]> 1308 { 1309 #[inline] 1310 fn from((x, y_is_odd): ([u8; <NistP384 as Curve>::FieldBytesSize::INT], bool)) -> Self { 1311 Self { x, y_is_odd } 1312 } 1313 } 1314 impl<'a: 'b, 'b> From<&'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>> 1315 for CompressedP384PubKey<&'b [u8; <NistP384 as Curve>::FieldBytesSize::INT]> 1316 { 1317 #[inline] 1318 fn from( 1319 value: &'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>, 1320 ) -> Self { 1321 Self { 1322 x: &value.x, 1323 y_is_odd: value.y_is_odd, 1324 } 1325 } 1326 } 1327 impl<'a: 'b, 'b> From<CompressedP384PubKey<&'a [u8; <NistP384 as Curve>::FieldBytesSize::INT]>> 1328 for CompressedP384PubKey<&'b [u8]> 1329 { 1330 #[inline] 1331 fn from( 1332 value: CompressedP384PubKey<&'a [u8; <NistP384 as Curve>::FieldBytesSize::INT]>, 1333 ) -> Self { 1334 Self { 1335 x: value.x.as_slice(), 1336 y_is_odd: value.y_is_odd, 1337 } 1338 } 1339 } 1340 impl<'a: 'b, 'b> From<&'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>> 1341 for CompressedP384PubKey<&'b [u8]> 1342 { 1343 #[inline] 1344 fn from( 1345 value: &'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>, 1346 ) -> Self { 1347 Self { 1348 x: value.x.as_slice(), 1349 y_is_odd: value.y_is_odd, 1350 } 1351 } 1352 } 1353 impl<'a: 'b, 'b> From<CompressedP384PubKey<&'a [u8]>> 1354 for CompressedP384PubKey<&'b [u8; <NistP384 as Curve>::FieldBytesSize::INT]> 1355 { 1356 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 1357 #[inline] 1358 fn from(value: CompressedP384PubKey<&'a [u8]>) -> Self { 1359 Self { 1360 x: value 1361 .x 1362 .try_into() 1363 .unwrap_or_else(|_e| unreachable!("&Array::try_from has a bug")), 1364 y_is_odd: value.y_is_odd, 1365 } 1366 } 1367 } 1368 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP384PubKey<T>> 1369 for CompressedP384PubKey<T2> 1370 { 1371 #[inline] 1372 fn eq(&self, other: &CompressedP384PubKey<T>) -> bool { 1373 self.x == other.x && self.y_is_odd == other.y_is_odd 1374 } 1375 } 1376 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP384PubKey<T>> 1377 for &CompressedP384PubKey<T2> 1378 { 1379 #[inline] 1380 fn eq(&self, other: &CompressedP384PubKey<T>) -> bool { 1381 **self == *other 1382 } 1383 } 1384 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&CompressedP384PubKey<T>> 1385 for CompressedP384PubKey<T2> 1386 { 1387 #[inline] 1388 fn eq(&self, other: &&CompressedP384PubKey<T>) -> bool { 1389 *self == **other 1390 } 1391 } 1392 impl<T: Eq> Eq for CompressedP384PubKey<T> {} 1393 /// The minimum RSA public key exponent allowed by [`RsaPubKey`]. 1394 /// 1395 /// [RFC 8017 § 3.1](https://www.rfc-editor.org/rfc/rfc8017#section-3.1) states the smallest valid RSA public 1396 /// exponent is 3. 1397 pub const MIN_RSA_E: u32 = 3; 1398 /// The most bits an RSA public key modulus is allowed to consist of per [`RsaPubKey`]. 1399 /// 1400 /// [RFC 8230 § 6.1](https://www.rfc-editor.org/rfc/rfc8230#section-6.1) recommends allowing moduli up to 16K bits. 1401 pub const MAX_RSA_N_BITS: usize = 0x4000; 1402 /// The fewest bits an RSA public key modulus is allowed to consist of per [`RsaPubKey`]. 1403 /// 1404 /// [RFC 8230 § 6.1](https://www.rfc-editor.org/rfc/rfc8230#section-6.1) requires the modulus to be at least 2048 1405 /// bits. 1406 pub const MIN_RSA_N_BITS: usize = 0x800; 1407 /// [`MIN_RSA_N_BITS`]–[`MAX_RSA_N_BITS`] bits representing the big-endian modulus and a `u32` `>=` 1408 /// [`MIN_RSA_E`] representing the exponent of an alleged RSA public key. 1409 /// 1410 /// Note the modulus and exponent are always odd. 1411 #[derive(Clone, Copy, Debug)] 1412 pub struct RsaPubKey<T>(T, u32); 1413 impl<T> RsaPubKey<T> { 1414 /// Returns [`Self::n`] and [`Self::e`] consuming `self`. 1415 #[inline] 1416 pub fn into_parts(self) -> (T, u32) { 1417 (self.0, self.1) 1418 } 1419 /// Returns [`Self::n`] and [`Self::e`]. 1420 #[inline] 1421 pub const fn as_parts(&self) -> (&T, u32) { 1422 (&self.0, self.1) 1423 } 1424 /// Returns the big-endian modulus. 1425 #[inline] 1426 pub const fn n(&self) -> &T { 1427 &self.0 1428 } 1429 /// Returns the exponent. 1430 #[inline] 1431 pub const fn e(&self) -> u32 { 1432 self.1 1433 } 1434 } 1435 impl<T: AsRef<[u8]>> RsaPubKey<T> { 1436 /// Converts `self` into [`RsaVerKey`]. 1437 pub(super) fn as_ver_key(&self) -> RsaVerKey<Sha256> { 1438 RsaVerKey::new(RsaPublicKey::new_unchecked( 1439 BigUint::from_bytes_be(self.0.as_ref()), 1440 self.1.into(), 1441 )) 1442 } 1443 } 1444 impl RsaPubKey<&[u8]> { 1445 /// Transforms `self` into an "owned" version. 1446 #[inline] 1447 #[must_use] 1448 pub fn into_owned(self) -> RsaPubKey<Vec<u8>> { 1449 RsaPubKey(self.0.to_owned(), self.1) 1450 } 1451 } 1452 impl<'a: 'b, 'b> TryFrom<(&'a [u8], u32)> for RsaPubKey<&'b [u8]> { 1453 type Error = RsaPubKeyErr; 1454 /// The first item is the big-endian modulus, and the second item is the exponent. 1455 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 1456 #[expect( 1457 clippy::arithmetic_side_effects, 1458 clippy::as_conversions, 1459 reason = "comment justifies correctness" 1460 )] 1461 #[inline] 1462 fn try_from((n, e): (&'a [u8], u32)) -> Result<Self, Self::Error> { 1463 n.first().map_or(Err(RsaPubKeyErr::NSize), |fst| { 1464 // `fst.leading_zeros()` is inclusively between `0` and `8`, so it's safe to convert to a 1465 // `usize`. 1466 let zeros = fst.leading_zeros() as usize; 1467 if zeros == 8 { 1468 Err(RsaPubKeyErr::NLeading0) 1469 // `bits` is at least 8 since `n.len()` is at least 1; thus underflow cannot occur. 1470 } else if let Some(bits) = n.len().checked_mul(8) 1471 && (MIN_RSA_N_BITS..=MAX_RSA_N_BITS).contains(&(bits - zeros)) 1472 { 1473 // We know `n` is not empty, so this won't `panic`. 1474 if n.last() 1475 .unwrap_or_else(|| unreachable!("there is a bug in RsaPubKey::try_from")) 1476 & 1 1477 == 0 1478 { 1479 Err(RsaPubKeyErr::NEven) 1480 } else if e < MIN_RSA_E { 1481 Err(RsaPubKeyErr::ESize) 1482 } else if e & 1 == 0 { 1483 Err(RsaPubKeyErr::EEven) 1484 } else { 1485 Ok(Self(n, e)) 1486 } 1487 } else { 1488 Err(RsaPubKeyErr::NSize) 1489 } 1490 }) 1491 } 1492 } 1493 impl TryFrom<(Vec<u8>, u32)> for RsaPubKey<Vec<u8>> { 1494 type Error = RsaPubKeyErr; 1495 /// Similar to [`RsaPubKey::try_from`] except `n` is a `Vec`. 1496 #[inline] 1497 fn try_from((n, e): (Vec<u8>, u32)) -> Result<Self, Self::Error> { 1498 match RsaPubKey::<&[u8]>::try_from((n.as_slice(), e)) { 1499 Ok(_) => Ok(Self(n, e)), 1500 Err(err) => Err(err), 1501 } 1502 } 1503 } 1504 impl<'a: 'b, 'b> From<&'a RsaPubKey<Vec<u8>>> for RsaPubKey<&'b Vec<u8>> { 1505 #[inline] 1506 fn from(value: &'a RsaPubKey<Vec<u8>>) -> Self { 1507 Self(&value.0, value.1) 1508 } 1509 } 1510 impl<'a: 'b, 'b> From<RsaPubKey<&'a Vec<u8>>> for RsaPubKey<&'b [u8]> { 1511 #[inline] 1512 fn from(value: RsaPubKey<&'a Vec<u8>>) -> Self { 1513 Self(value.0.as_slice(), value.1) 1514 } 1515 } 1516 impl<'a: 'b, 'b> From<&'a RsaPubKey<Vec<u8>>> for RsaPubKey<&'b [u8]> { 1517 #[inline] 1518 fn from(value: &'a RsaPubKey<Vec<u8>>) -> Self { 1519 Self(value.0.as_slice(), value.1) 1520 } 1521 } 1522 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<RsaPubKey<T>> for RsaPubKey<T2> { 1523 #[inline] 1524 fn eq(&self, other: &RsaPubKey<T>) -> bool { 1525 self.0 == other.0 && self.1 == other.1 1526 } 1527 } 1528 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<RsaPubKey<T>> for &RsaPubKey<T2> { 1529 #[inline] 1530 fn eq(&self, other: &RsaPubKey<T>) -> bool { 1531 **self == *other 1532 } 1533 } 1534 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&RsaPubKey<T>> for RsaPubKey<T2> { 1535 #[inline] 1536 fn eq(&self, other: &&RsaPubKey<T>) -> bool { 1537 *self == **other 1538 } 1539 } 1540 impl<T: Eq> Eq for RsaPubKey<T> {} 1541 /// `kty` COSE key common parameter as defined by 1542 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters). 1543 const KTY: u8 = cbor::ONE; 1544 /// `OKP` COSE key type as defined by 1545 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type). 1546 const OKP: u8 = cbor::ONE; 1547 /// `EC2` COSE key type as defined by 1548 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type). 1549 const EC2: u8 = cbor::TWO; 1550 /// `RSA` COSE key type as defined by 1551 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type). 1552 const RSA: u8 = cbor::THREE; 1553 /// `alg` COSE key common parameter as defined by 1554 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters). 1555 const ALG: u8 = cbor::THREE; 1556 /// `EdDSA` COSE algorithm as defined by 1557 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 1558 const EDDSA: u8 = cbor::NEG_EIGHT; 1559 /// `ES256` COSE algorithm as defined by 1560 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 1561 const ES256: u8 = cbor::NEG_SEVEN; 1562 /// `ES384` COSE algorithm as defined by 1563 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 1564 /// 1565 /// This is -35 encoded in cbor which is encoded as |-35| - 1 = 35 - 1 = 34. Note 1566 /// this must be preceded with `cbor::NEG_INFO_24`. 1567 const ES384: u8 = 34; 1568 /// `RS256` COSE algorithm as defined by 1569 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 1570 /// 1571 /// This is -257 encoded in cbor which is encoded as |-257| - 1 = 257 - 1 = 256 = [1, 0] in big endian. 1572 /// Note this must be preceded with `cbor::NEG_INFO_25`. 1573 const RS256: [u8; 2] = [1, 0]; 1574 impl<'a> FromCbor<'a> for Ed25519PubKey<&'a [u8]> { 1575 type Err = CoseKeyErr; 1576 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 1577 /// `crv` COSE key type parameter for [`OKP`] as defined by 1578 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1579 const CRV: u8 = cbor::NEG_ONE; 1580 /// `Ed25519` COSE elliptic curve as defined by 1581 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves). 1582 const ED25519: u8 = cbor::SIX; 1583 /// `x` COSE key type parameter for [`OKP`] as defined by 1584 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1585 const X: u8 = cbor::NEG_TWO; 1586 // `32 as u8` is OK. 1587 /// `ed25519_dalek::PUBLIC_KEY_LENGTH` as a `u8`. 1588 #[expect( 1589 clippy::as_conversions, 1590 clippy::cast_possible_truncation, 1591 reason = "explained above and want a const" 1592 )] 1593 const KEY_LEN_U8: u8 = ed25519_dalek::PUBLIC_KEY_LENGTH as u8; 1594 /// COSE header. 1595 /// {kty:OKP,alg:EdDSA,crv:Ed25519,x:<CompressedEdwardsYPoint>}. 1596 /// `kty` and `alg` come before `crv` and `x` since map order first 1597 /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s. 1598 /// `kty` comes before `alg` since order is done byte-wise and 1599 /// 1 is before 3. `crv` is before `x` since `0b001_00000` comes before 1600 /// `0b001_00001` byte-wise. 1601 const HEADER: [u8; 10] = [ 1602 cbor::MAP_4, 1603 KTY, 1604 OKP, 1605 ALG, 1606 EDDSA, 1607 CRV, 1608 ED25519, 1609 X, 1610 cbor::BYTES_INFO_24, 1611 KEY_LEN_U8, 1612 ]; 1613 cbor.split_at_checked(HEADER.len()) 1614 .ok_or(CoseKeyErr::Len) 1615 .and_then(|(header, header_rem)| { 1616 if header == HEADER { 1617 header_rem 1618 .split_at_checked(ed25519_dalek::PUBLIC_KEY_LENGTH) 1619 .ok_or(CoseKeyErr::Len) 1620 .map(|(key, remaining)| CborSuccess { 1621 value: Self(key), 1622 remaining, 1623 }) 1624 } else { 1625 Err(CoseKeyErr::Ed25519CoseEncoding) 1626 } 1627 }) 1628 } 1629 } 1630 impl<'a> FromCbor<'a> for UncompressedP256PubKey<'a> { 1631 type Err = CoseKeyErr; 1632 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 1633 /// `crv` COSE key type parameter for [`EC2`] as defined by 1634 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1635 const CRV: u8 = cbor::NEG_ONE; 1636 /// `P-256` COSE elliptic curve as defined by 1637 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves). 1638 const P256: u8 = cbor::ONE; 1639 /// `x` COSE key type parameter for [`EC2`] as defined by 1640 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1641 const X: u8 = cbor::NEG_TWO; 1642 /// `y` COSE key type parameter for [`EC2`] as defined by 1643 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1644 const Y: u8 = cbor::NEG_THREE; 1645 /// Number of bytes the x-coordinate takes. 1646 const X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; 1647 // `32 as u8` is OK. 1648 /// `X_LEN` as a `u8`. 1649 #[expect( 1650 clippy::as_conversions, 1651 clippy::cast_possible_truncation, 1652 reason = "explained above and want a const" 1653 )] 1654 const X_LEN_U8: u8 = X_LEN as u8; 1655 /// Number of bytes the y-coordinate takes. 1656 const Y_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; 1657 // `32 as u8` is OK. 1658 /// `Y_LEN` as a `u8`. 1659 #[expect( 1660 clippy::as_conversions, 1661 clippy::cast_possible_truncation, 1662 reason = "explained above and want a const" 1663 )] 1664 const Y_LEN_U8: u8 = Y_LEN as u8; 1665 /// COSE header. 1666 // {kty:EC2,alg:ES256,crv:P-256,x:<affine x-coordinate>,...}. 1667 /// `kty` and `alg` come before `crv`, `x`, and `y` since map order first 1668 /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s. 1669 /// `kty` comes before `alg` since order is done byte-wise and 1670 /// 1 is before 3. `crv` is before `x` which is before `y` since 1671 /// `0b001_00000` comes before `0b001_00001` which comes before 1672 /// `0b001_00010` byte-wise. 1673 const HEADER: [u8; 10] = [ 1674 cbor::MAP_5, 1675 KTY, 1676 EC2, 1677 ALG, 1678 ES256, 1679 CRV, 1680 P256, 1681 X, 1682 cbor::BYTES_INFO_24, 1683 X_LEN_U8, 1684 ]; 1685 /// {...y:<affine y-coordinate>}. 1686 const Y_META: [u8; 3] = [Y, cbor::BYTES_INFO_24, Y_LEN_U8]; 1687 cbor.split_at_checked(HEADER.len()) 1688 .ok_or(CoseKeyErr::Len) 1689 .and_then(|(header, header_rem)| { 1690 if header == HEADER { 1691 header_rem 1692 .split_at_checked(X_LEN) 1693 .ok_or(CoseKeyErr::Len) 1694 .and_then(|(x, x_rem)| { 1695 x_rem 1696 .split_at_checked(Y_META.len()) 1697 .ok_or(CoseKeyErr::Len) 1698 .and_then(|(y_meta, y_meta_rem)| { 1699 if y_meta == Y_META { 1700 y_meta_rem 1701 .split_at_checked(Y_LEN) 1702 .ok_or(CoseKeyErr::Len) 1703 .map(|(y, remaining)| CborSuccess { 1704 value: Self(x, y), 1705 remaining, 1706 }) 1707 } else { 1708 Err(CoseKeyErr::P256CoseEncoding) 1709 } 1710 }) 1711 }) 1712 } else { 1713 Err(CoseKeyErr::P256CoseEncoding) 1714 } 1715 }) 1716 } 1717 } 1718 impl<'a> FromCbor<'a> for UncompressedP384PubKey<'a> { 1719 type Err = CoseKeyErr; 1720 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 1721 /// `crv` COSE key type parameter for [`EC2`] as defined by 1722 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1723 const CRV: u8 = cbor::NEG_ONE; 1724 /// `P-384` COSE elliptic curve as defined by 1725 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves). 1726 const P384: u8 = cbor::TWO; 1727 /// `x` COSE key type parameter for [`EC2`] as defined by 1728 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1729 const X: u8 = cbor::NEG_TWO; 1730 /// `y` COSE key type parameter for [`EC2`] as defined by 1731 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1732 const Y: u8 = cbor::NEG_THREE; 1733 /// Number of bytes the x-coordinate takes. 1734 const X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; 1735 // `48 as u8` is OK. 1736 /// `X_LEN` as a `u8`. 1737 #[expect( 1738 clippy::as_conversions, 1739 clippy::cast_possible_truncation, 1740 reason = "explained above and want a const" 1741 )] 1742 const X_LEN_U8: u8 = X_LEN as u8; 1743 /// Number of bytes the y-coordinate takes. 1744 const Y_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; 1745 // `48 as u8` is OK. 1746 /// `Y_LEN` as a `u8`. 1747 #[expect( 1748 clippy::as_conversions, 1749 clippy::cast_possible_truncation, 1750 reason = "explained above and want a const" 1751 )] 1752 const Y_LEN_U8: u8 = Y_LEN as u8; 1753 /// COSE header. 1754 // {kty:EC2,alg:ES384,crv:P-384,x:<affine x-coordinate>,...}. 1755 /// `kty` and `alg` come before `crv`, `x`, and `y` since map order first 1756 /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s. 1757 /// `kty` comes before `alg` since order is done byte-wise and 1758 /// 1 is before 3. `crv` is before `x` which is before `y` since 1759 /// `0b001_00000` comes before `0b001_00001` which comes before 1760 /// `0b001_00010` byte-wise. 1761 const HEADER: [u8; 11] = [ 1762 cbor::MAP_5, 1763 KTY, 1764 EC2, 1765 ALG, 1766 cbor::NEG_INFO_24, 1767 ES384, 1768 CRV, 1769 P384, 1770 X, 1771 cbor::BYTES_INFO_24, 1772 X_LEN_U8, 1773 ]; 1774 /// {...y:<affine y-coordinate>}. 1775 const Y_META: [u8; 3] = [Y, cbor::BYTES_INFO_24, Y_LEN_U8]; 1776 cbor.split_at_checked(HEADER.len()) 1777 .ok_or(CoseKeyErr::Len) 1778 .and_then(|(header, header_rem)| { 1779 if header == HEADER { 1780 header_rem 1781 .split_at_checked(X_LEN) 1782 .ok_or(CoseKeyErr::Len) 1783 .and_then(|(x, x_rem)| { 1784 x_rem 1785 .split_at_checked(Y_META.len()) 1786 .ok_or(CoseKeyErr::Len) 1787 .and_then(|(y_meta, y_meta_rem)| { 1788 if y_meta == Y_META { 1789 y_meta_rem 1790 .split_at_checked(Y_LEN) 1791 .ok_or(CoseKeyErr::Len) 1792 .map(|(y, remaining)| CborSuccess { 1793 value: Self(x, y), 1794 remaining, 1795 }) 1796 } else { 1797 Err(CoseKeyErr::P384CoseEncoding) 1798 } 1799 }) 1800 }) 1801 } else { 1802 Err(CoseKeyErr::P384CoseEncoding) 1803 } 1804 }) 1805 } 1806 } 1807 impl<'a> FromCbor<'a> for RsaPubKey<&'a [u8]> { 1808 type Err = CoseKeyErr; 1809 #[expect( 1810 clippy::arithmetic_side_effects, 1811 clippy::big_endian_bytes, 1812 clippy::indexing_slicing, 1813 clippy::missing_asserts_for_indexing, 1814 reason = "comments justify their correctness" 1815 )] 1816 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 1817 /// `n` COSE key type parameter for [`RSA`] as defined by 1818 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1819 const N: u8 = cbor::NEG_ONE; 1820 /// `e` COSE key type parameter for [`RSA`] as defined by 1821 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters). 1822 const E: u8 = cbor::NEG_TWO; 1823 /// COSE header. 1824 /// {kty:RSA,alg:RS256,n:<RSA modulus>,...}. 1825 /// `kty` and `alg` come before `n` and `e` since map order first 1826 /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s. 1827 /// `kty` comes before `alg` since order is done byte-wise and 1828 /// 1 is before 3. `n` is before `e` since `0b001_00000` comes before 1829 /// `0b001_00001` byte-wise. 1830 /// 1831 /// Note `RS256` COSE algorithm as defined by 1832 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 1833 /// is encoded as -257 which is encoded in CBOR as `[cbor::NEG_INFO_25, 1, 0]` since 1834 /// |-257| - 1 = 256 which takes two bytes to encode as 1, 0 in big-endian. 1835 /// Ditto for a byte string of length 0x100 to 0xFFFF inclusively replacing `cbor::NEG_INFO_25` with 1836 /// `cbor::BYTES_INFO_25`. 1837 /// 1838 /// Recall that [`RsaPubKey`] requires the modulus to be at least 256 bytes in length but no greater than 1839 /// 2048 bytes in length; thus we know a valid and allowed `n` will have length whose metadata 1840 /// takes exactly two bytes. 1841 const HEADER: [u8; 9] = [ 1842 cbor::MAP_4, 1843 KTY, 1844 RSA, 1845 ALG, 1846 cbor::NEG_INFO_25, 1847 1, 1848 0, 1849 N, 1850 cbor::BYTES_INFO_25, 1851 ]; 1852 cbor.split_at_checked(HEADER.len()).ok_or(CoseKeyErr::Len).and_then(|(header, header_rem)| { 1853 if header == HEADER { 1854 header_rem.split_at_checked(2).ok_or(CoseKeyErr::Len).and_then(|(n_len_slice, n_len_rem)| { 1855 let mut len = [0; 2]; 1856 len.copy_from_slice(n_len_slice); 1857 // cbor uints are in big-endian. 1858 let n_len = usize::from(u16::from_be_bytes(len)); 1859 if n_len > 255 { 1860 n_len_rem.split_at_checked(n_len).ok_or(CoseKeyErr::Len).and_then(|(n, n_rem)| { 1861 n_rem.split_at_checked(2).ok_or(CoseKeyErr::RsaCoseEncoding).and_then(|(e_meta, e_meta_rem)| { 1862 // `e_meta.len() == 2`, so this is fine. 1863 if e_meta[0] == E { 1864 // `e_meta.len() == 2`, so this is fine. 1865 let e_meta_len = e_meta[1]; 1866 if e_meta_len & cbor::BYTES == cbor::BYTES { 1867 let e_len = usize::from(e_meta_len ^ cbor::BYTES); 1868 if e_len < 5 { 1869 e_meta_rem.split_at_checked(e_len).ok_or(CoseKeyErr::Len).and_then(|(e_slice, remaining)| { 1870 e_slice.first().ok_or(CoseKeyErr::Len).and_then(|e_first| { 1871 // We ensure the leading byte is not 0; otherwise the 1872 // exponent is not properly encoded. Note the exponent 1873 // can never be 0. 1874 if *e_first > 0 { 1875 let mut e = [0; 4]; 1876 // `e_slice.len()` is `e_len` which is less than 5. 1877 // We also know it is greater than 0 since `e_slice.first()` did not err. 1878 // Thus this won't `panic`. 1879 e[4 - e_len..].copy_from_slice(e_slice); 1880 Self::try_from((n, u32::from_be_bytes(e))).map_err(CoseKeyErr::RsaPubKey).map(|value| CborSuccess { value, remaining, } ) 1881 } else { 1882 Err(CoseKeyErr::RsaCoseEncoding) 1883 } 1884 }) 1885 }) 1886 } else { 1887 Err(CoseKeyErr::RsaExponentTooLarge) 1888 } 1889 } else { 1890 Err(CoseKeyErr::RsaCoseEncoding) 1891 } 1892 } else { 1893 Err(CoseKeyErr::RsaCoseEncoding) 1894 } 1895 }) 1896 }) 1897 } else { 1898 Err(CoseKeyErr::RsaCoseEncoding) 1899 } 1900 }) 1901 } else { 1902 Err(CoseKeyErr::RsaCoseEncoding) 1903 } 1904 }) 1905 } 1906 } 1907 /// An alleged uncompressed public key that borrows the key data. 1908 /// 1909 /// Note [`Self::Ed25519`] is compressed. 1910 #[derive(Clone, Copy, Debug)] 1911 pub enum UncompressedPubKey<'a> { 1912 /// An alleged Ed25519 public key. 1913 Ed25519(Ed25519PubKey<&'a [u8]>), 1914 /// An alleged uncompressed P-256 public key. 1915 P256(UncompressedP256PubKey<'a>), 1916 /// An alleged uncompressed P-384 public key. 1917 P384(UncompressedP384PubKey<'a>), 1918 /// An alleged RSA public key. 1919 Rsa(RsaPubKey<&'a [u8]>), 1920 } 1921 impl UncompressedPubKey<'_> { 1922 /// Validates `self` is in fact a valid public key. 1923 /// 1924 /// # Errors 1925 /// 1926 /// Errors iff `self` is not a valid public key. 1927 #[inline] 1928 pub fn validate(self) -> Result<(), PubKeyErr> { 1929 match self { 1930 Self::Ed25519(k) => k.validate(), 1931 Self::P256(k) => k.validate(), 1932 Self::P384(k) => k.validate(), 1933 Self::Rsa(_) => Ok(()), 1934 } 1935 } 1936 /// Transforms `self` into the compressed version that owns the data. 1937 #[inline] 1938 #[must_use] 1939 pub fn into_compressed( 1940 self, 1941 ) -> CompressedPubKey< 1942 [u8; ed25519_dalek::PUBLIC_KEY_LENGTH], 1943 [u8; <NistP256 as Curve>::FieldBytesSize::INT], 1944 [u8; <NistP384 as Curve>::FieldBytesSize::INT], 1945 Vec<u8>, 1946 > { 1947 match self { 1948 Self::Ed25519(key) => CompressedPubKey::Ed25519(key.into_owned()), 1949 Self::P256(key) => CompressedPubKey::P256(key.into_compressed()), 1950 Self::P384(key) => CompressedPubKey::P384(key.into_compressed()), 1951 Self::Rsa(key) => CompressedPubKey::Rsa(key.into_owned()), 1952 } 1953 } 1954 } 1955 impl PartialEq<UncompressedPubKey<'_>> for UncompressedPubKey<'_> { 1956 #[inline] 1957 fn eq(&self, other: &UncompressedPubKey<'_>) -> bool { 1958 match *self { 1959 Self::Ed25519(k) => matches!(*other, UncompressedPubKey::Ed25519(k2) if k == k2), 1960 Self::P256(k) => matches!(*other, UncompressedPubKey::P256(k2) if k == k2), 1961 Self::P384(k) => matches!(*other, UncompressedPubKey::P384(k2) if k == k2), 1962 Self::Rsa(k) => matches!(*other, UncompressedPubKey::Rsa(k2) if k == k2), 1963 } 1964 } 1965 } 1966 impl PartialEq<&UncompressedPubKey<'_>> for UncompressedPubKey<'_> { 1967 #[inline] 1968 fn eq(&self, other: &&UncompressedPubKey<'_>) -> bool { 1969 *self == **other 1970 } 1971 } 1972 impl PartialEq<UncompressedPubKey<'_>> for &UncompressedPubKey<'_> { 1973 #[inline] 1974 fn eq(&self, other: &UncompressedPubKey<'_>) -> bool { 1975 **self == *other 1976 } 1977 } 1978 impl Eq for UncompressedPubKey<'_> {} 1979 /// An alleged compressed public key. 1980 /// 1981 /// Note [`Self::Rsa`] is uncompressed. 1982 #[derive(Clone, Copy, Debug)] 1983 pub enum CompressedPubKey<T, T2, T3, T4> { 1984 /// An alleged Ed25519 public key. 1985 Ed25519(Ed25519PubKey<T>), 1986 /// An alleged compressed P-256 public key. 1987 P256(CompressedP256PubKey<T2>), 1988 /// An alleged compressed P-384 public key. 1989 P384(CompressedP384PubKey<T3>), 1990 /// An alleged RSA public key. 1991 Rsa(RsaPubKey<T4>), 1992 } 1993 /// `CompressedPubKey` that owns the key data. 1994 pub type CompressedPubKeyOwned = CompressedPubKey< 1995 [u8; ed25519_dalek::PUBLIC_KEY_LENGTH], 1996 [u8; <NistP256 as Curve>::FieldBytesSize::INT], 1997 [u8; <NistP384 as Curve>::FieldBytesSize::INT], 1998 Vec<u8>, 1999 >; 2000 /// `CompressedPubKey` that borrows the key data. 2001 pub type CompressedPubKeyBorrowed<'a> = CompressedPubKey<&'a [u8], &'a [u8], &'a [u8], &'a [u8]>; 2002 impl CompressedPubKey<&[u8], &[u8], &[u8], &[u8]> { 2003 /// Validates `self` is in fact a valid public key. 2004 /// 2005 /// # Errors 2006 /// 2007 /// Errors iff `self` is not a valid public key. 2008 #[inline] 2009 pub fn validate(self) -> Result<(), PubKeyErr> { 2010 match self { 2011 Self::Ed25519(k) => k.validate(), 2012 Self::P256(k) => k.validate(), 2013 Self::P384(k) => k.validate(), 2014 Self::Rsa(_) => Ok(()), 2015 } 2016 } 2017 } 2018 impl<'a: 'b, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>, T3: AsRef<[u8]>, T4: AsRef<[u8]>> 2019 From<&'a CompressedPubKey<T, T2, T3, T4>> 2020 for CompressedPubKey<&'b [u8], &'b [u8], &'b [u8], &'b [u8]> 2021 { 2022 #[inline] 2023 fn from(value: &'a CompressedPubKey<T, T2, T3, T4>) -> Self { 2024 match *value { 2025 CompressedPubKey::Ed25519(ref val) => Self::Ed25519(Ed25519PubKey(val.0.as_ref())), 2026 CompressedPubKey::P256(ref val) => Self::P256(CompressedP256PubKey { 2027 x: val.x.as_ref(), 2028 y_is_odd: val.y_is_odd, 2029 }), 2030 CompressedPubKey::P384(ref val) => Self::P384(CompressedP384PubKey { 2031 x: val.x.as_ref(), 2032 y_is_odd: val.y_is_odd, 2033 }), 2034 CompressedPubKey::Rsa(ref val) => Self::Rsa(RsaPubKey(val.0.as_ref(), val.1)), 2035 } 2036 } 2037 } 2038 impl< 2039 T: PartialEq<T5>, 2040 T5: PartialEq<T>, 2041 T2: PartialEq<T6>, 2042 T6: PartialEq<T2>, 2043 T3: PartialEq<T7>, 2044 T7: PartialEq<T3>, 2045 T4: PartialEq<T8>, 2046 T8: PartialEq<T4>, 2047 > PartialEq<CompressedPubKey<T, T2, T3, T4>> for CompressedPubKey<T5, T6, T7, T8> 2048 { 2049 #[inline] 2050 fn eq(&self, other: &CompressedPubKey<T, T2, T3, T4>) -> bool { 2051 match *self { 2052 Self::Ed25519(ref val) => { 2053 matches!(*other, CompressedPubKey::Ed25519(ref val2) if val == val2) 2054 } 2055 Self::P256(ref val) => { 2056 matches!(*other, CompressedPubKey::P256(ref val2) if val == val2) 2057 } 2058 Self::P384(ref val) => { 2059 matches!(*other, CompressedPubKey::P384(ref val2) if val == val2) 2060 } 2061 Self::Rsa(ref val) => matches!(*other, CompressedPubKey::Rsa(ref val2) if val == val2), 2062 } 2063 } 2064 } 2065 impl< 2066 T: PartialEq<T5>, 2067 T5: PartialEq<T>, 2068 T2: PartialEq<T6>, 2069 T6: PartialEq<T2>, 2070 T3: PartialEq<T7>, 2071 T7: PartialEq<T3>, 2072 T4: PartialEq<T8>, 2073 T8: PartialEq<T4>, 2074 > PartialEq<CompressedPubKey<T, T2, T3, T4>> for &CompressedPubKey<T5, T6, T7, T8> 2075 { 2076 #[inline] 2077 fn eq(&self, other: &CompressedPubKey<T, T2, T3, T4>) -> bool { 2078 **self == *other 2079 } 2080 } 2081 impl< 2082 T: PartialEq<T5>, 2083 T5: PartialEq<T>, 2084 T2: PartialEq<T6>, 2085 T6: PartialEq<T2>, 2086 T3: PartialEq<T7>, 2087 T7: PartialEq<T3>, 2088 T4: PartialEq<T8>, 2089 T8: PartialEq<T4>, 2090 > PartialEq<&CompressedPubKey<T, T2, T3, T4>> for CompressedPubKey<T5, T6, T7, T8> 2091 { 2092 #[inline] 2093 fn eq(&self, other: &&CompressedPubKey<T, T2, T3, T4>) -> bool { 2094 *self == **other 2095 } 2096 } 2097 impl<T: Eq, T2: Eq, T3: Eq, T4: Eq> Eq for CompressedPubKey<T, T2, T3, T4> {} 2098 impl<'a> FromCbor<'a> for UncompressedPubKey<'a> { 2099 type Err = CoseKeyErr; 2100 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2101 // {kty:<type>...}. 2102 cbor.get(2) 2103 .ok_or(CoseKeyErr::Len) 2104 .and_then(|kty| match *kty { 2105 OKP => Ed25519PubKey::from_cbor(cbor).map(|key| CborSuccess { 2106 value: Self::Ed25519(key.value), 2107 remaining: key.remaining, 2108 }), 2109 // {kty:EC2,alg:ES256|ES384,...} 2110 EC2 => cbor.get(4).ok_or(CoseKeyErr::Len).and_then(|alg| { 2111 if *alg == ES256 { 2112 UncompressedP256PubKey::from_cbor(cbor).map(|key| CborSuccess { 2113 value: Self::P256(key.value), 2114 remaining: key.remaining, 2115 }) 2116 } else { 2117 UncompressedP384PubKey::from_cbor(cbor).map(|key| CborSuccess { 2118 value: Self::P384(key.value), 2119 remaining: key.remaining, 2120 }) 2121 } 2122 }), 2123 RSA => RsaPubKey::from_cbor(cbor).map(|key| CborSuccess { 2124 value: Self::Rsa(key.value), 2125 remaining: key.remaining, 2126 }), 2127 _ => Err(CoseKeyErr::CoseKeyType), 2128 }) 2129 } 2130 } 2131 /// Length of AAGUID. 2132 const AAGUID_LEN: usize = 16; 2133 /// 16 bytes representing an 2134 /// [Authenticator Attestation Globally Unique Identifier (AAGUID)](https://www.w3.org/TR/webauthn-3/#aaguid). 2135 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2136 pub struct Aaguid<'a>(&'a [u8]); 2137 impl<'a> Aaguid<'a> { 2138 /// Returns the contained data. 2139 #[inline] 2140 #[must_use] 2141 pub const fn data(self) -> &'a [u8] { 2142 self.0 2143 } 2144 } 2145 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Aaguid<'b> { 2146 type Error = AaguidErr; 2147 #[inline] 2148 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { 2149 if value.len() == AAGUID_LEN { 2150 Ok(Self(value)) 2151 } else { 2152 Err(AaguidErr) 2153 } 2154 } 2155 } 2156 /// [Attested credential data](https://www.w3.org/TR/webauthn-3/#attested-credential-data). 2157 #[derive(Debug)] 2158 pub struct AttestedCredentialData<'a> { 2159 /// [`aaguid`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-aaguid). 2160 pub aaguid: Aaguid<'a>, 2161 /// [`credentialId`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialid). 2162 pub credential_id: CredentialId<&'a [u8]>, 2163 /// [`credentialPublicKey`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialpublickey). 2164 pub credential_public_key: UncompressedPubKey<'a>, 2165 } 2166 impl<'a> FromCbor<'a> for AttestedCredentialData<'a> { 2167 type Err = AttestedCredentialDataErr; 2168 #[expect(clippy::big_endian_bytes, reason = "CBOR integers are big-endian")] 2169 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2170 /// Number of bytes the `CredentialId` length is encoded into. 2171 const CRED_LEN_LEN: usize = 2; 2172 cbor.split_at_checked(AAGUID_LEN) 2173 .ok_or(AttestedCredentialDataErr::Len) 2174 .and_then(|(aaguid, aaguid_rem)| { 2175 aaguid_rem 2176 .split_at_checked(CRED_LEN_LEN) 2177 .ok_or(AttestedCredentialDataErr::Len) 2178 .and_then(|(cred_len_slice, cred_len_rem)| { 2179 let mut cred_len = [0; CRED_LEN_LEN]; 2180 cred_len.copy_from_slice(cred_len_slice); 2181 // `credentialIdLength` is in big-endian. 2182 cred_len_rem 2183 .split_at_checked(usize::from(u16::from_be_bytes(cred_len))) 2184 .ok_or(AttestedCredentialDataErr::Len) 2185 .and_then(|(cred_id, cred_id_rem)| { 2186 CredentialId::from_slice(cred_id) 2187 .map_err(AttestedCredentialDataErr::CredentialId) 2188 .and_then(|credential_id| { 2189 UncompressedPubKey::from_cbor(cred_id_rem) 2190 .map_err(AttestedCredentialDataErr::CoseKey) 2191 .map(|cose| CborSuccess { 2192 value: Self { 2193 aaguid: Aaguid(aaguid), 2194 credential_id, 2195 credential_public_key: cose.value, 2196 }, 2197 remaining: cose.remaining, 2198 }) 2199 }) 2200 }) 2201 }) 2202 }) 2203 } 2204 } 2205 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data). 2206 #[derive(Debug)] 2207 pub struct AuthenticatorData<'a> { 2208 /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash). 2209 rp_id_hash: &'a [u8], 2210 /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags). 2211 flags: Flag, 2212 /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount). 2213 sign_count: u32, 2214 /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata). 2215 attested_credential_data: AttestedCredentialData<'a>, 2216 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions). 2217 extensions: AuthenticatorExtensionOutput, 2218 } 2219 impl<'a> AuthenticatorData<'a> { 2220 /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash). 2221 #[inline] 2222 #[must_use] 2223 pub const fn rp_id_hash(&self) -> &'a [u8] { 2224 self.rp_id_hash 2225 } 2226 /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags). 2227 #[inline] 2228 #[must_use] 2229 pub const fn flags(&self) -> Flag { 2230 self.flags 2231 } 2232 /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount). 2233 #[inline] 2234 #[must_use] 2235 pub const fn sign_count(&self) -> u32 { 2236 self.sign_count 2237 } 2238 /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata). 2239 #[inline] 2240 #[must_use] 2241 pub const fn attested_credential_data(&self) -> &AttestedCredentialData<'a> { 2242 &self.attested_credential_data 2243 } 2244 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions). 2245 #[inline] 2246 #[must_use] 2247 pub const fn extensions(&self) -> AuthenticatorExtensionOutput { 2248 self.extensions 2249 } 2250 } 2251 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for AuthenticatorData<'b> { 2252 type Error = AuthenticatorDataErr; 2253 /// Deserializes `value` based on the 2254 /// [authenticator data structure](https://www.w3.org/TR/webauthn-3/#table-authData). 2255 #[expect( 2256 clippy::panic_in_result_fn, 2257 reason = "we want to crash when there is a bug" 2258 )] 2259 #[inline] 2260 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { 2261 Self::from_cbor(value) 2262 .map_err(AuthenticatorDataErr::from) 2263 .map(|success| { 2264 assert!( 2265 success.remaining.is_empty(), 2266 "there is a bug in AuthenticatorData::from_cbor" 2267 ); 2268 success.value 2269 }) 2270 } 2271 } 2272 impl<'a> AuthData<'a> for AuthenticatorData<'a> { 2273 type UpBitErr = Infallible; 2274 type CredData = AttestedCredentialData<'a>; 2275 type Ext = AuthenticatorExtensionOutput; 2276 fn contains_at_bit() -> bool { 2277 true 2278 } 2279 fn user_is_not_present() -> Result<(), Self::UpBitErr> { 2280 Ok(()) 2281 } 2282 fn new( 2283 rp_id_hash: &'a [u8], 2284 flags: Flag, 2285 sign_count: u32, 2286 attested_credential_data: Self::CredData, 2287 extensions: Self::Ext, 2288 ) -> Self { 2289 Self { 2290 rp_id_hash, 2291 flags, 2292 sign_count, 2293 attested_credential_data, 2294 extensions, 2295 } 2296 } 2297 fn rp_hash(&self) -> &'a [u8] { 2298 self.rp_id_hash 2299 } 2300 fn flag(&self) -> Flag { 2301 self.flags 2302 } 2303 } 2304 /// [None](https://www.w3.org/TR/webauthn-3/#sctn-none-attestation). 2305 struct NoneAttestation; 2306 impl FromCbor<'_> for NoneAttestation { 2307 type Err = AttestationErr; 2308 fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> { 2309 cbor.split_first() 2310 .ok_or(AttestationErr::Len) 2311 .and_then(|(map, remaining)| { 2312 if *map == cbor::MAP_0 { 2313 Ok(CborSuccess { 2314 value: Self, 2315 remaining, 2316 }) 2317 } else { 2318 Err(AttestationErr::NoneFormat) 2319 } 2320 }) 2321 } 2322 } 2323 /// A 64-byte slice that allegedly represents an Ed25519 signature. 2324 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2325 pub struct Ed25519Signature<'a>(&'a [u8]); 2326 impl<'a> Ed25519Signature<'a> { 2327 /// Returns signature. 2328 #[inline] 2329 #[must_use] 2330 pub const fn data(self) -> &'a [u8] { 2331 self.0 2332 } 2333 /// Transforms `self` into `Signature`. 2334 fn into_sig(self) -> Signature { 2335 let mut sig = [0; ed25519_dalek::SIGNATURE_LENGTH]; 2336 sig.copy_from_slice(self.0); 2337 Signature::from_bytes(&sig) 2338 } 2339 } 2340 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Ed25519Signature<'b> { 2341 type Error = Ed25519SignatureErr; 2342 /// Interprets `value` as an Ed25519 signature. 2343 /// 2344 /// # Errors 2345 /// 2346 /// Errors iff `value.len() != 64`. 2347 #[inline] 2348 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { 2349 if value.len() == ed25519_dalek::SIGNATURE_LENGTH { 2350 Ok(Self(value)) 2351 } else { 2352 Err(Ed25519SignatureErr) 2353 } 2354 } 2355 } 2356 impl<'a> FromCbor<'a> for Ed25519Signature<'a> { 2357 type Err = AttestationErr; 2358 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2359 // `64 as u8` is OK. 2360 /// CBOR metadata describing the signature. 2361 #[expect( 2362 clippy::as_conversions, 2363 clippy::cast_possible_truncation, 2364 reason = "comments justify their correctness" 2365 )] 2366 const HEADER: [u8; 2] = [cbor::BYTES_INFO_24, ed25519_dalek::SIGNATURE_LENGTH as u8]; 2367 cbor.split_at_checked(HEADER.len()) 2368 .ok_or(AttestationErr::Len) 2369 .and_then(|(header, header_rem)| { 2370 if header == HEADER { 2371 header_rem 2372 .split_at_checked(ed25519_dalek::SIGNATURE_LENGTH) 2373 .ok_or(AttestationErr::Len) 2374 .map(|(sig, remaining)| CborSuccess { 2375 value: Self(sig), 2376 remaining, 2377 }) 2378 } else { 2379 Err(AttestationErr::PackedFormatCborEd25519Signature) 2380 } 2381 }) 2382 } 2383 } 2384 /// An alleged DER-encoded P-256 signature using SHA-256. 2385 struct P256DerSig<'a>(&'a [u8]); 2386 impl<'a> FromCbor<'a> for P256DerSig<'a> { 2387 type Err = AttestationErr; 2388 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2389 // ```asn 2390 // Signature ::= SEQUENCE { 2391 // r INTEGER, 2392 // s INTEGER 2393 // } 2394 // ``` 2395 // We assume `r` and `s` can be _any_ 32-byte unsigned integer; thus when DER-encoded without metadata, 2396 // the number of bytes is inclusively between 1 and 33—INTEGER is a signed integer; thus a 0 byte must 2397 // be prepended iff the high bit is 1 (i.e., 32-byte unsigned integers with the high bit set take 33 bytes). 2398 // With metadata this makes the lengths inclusively between 3 and 35—we add 1 for the INTEGER tag and 1 2399 // for the number of bytes it takes to encode the integer. The total encoded length is thus inclusively 2400 // between 8 = 1 + 1 + 2(3) and 72 = 1 + 1 + 2(35)—we add 1 for the CONSTRUCTED SEQUENCE tag and 1 for 2401 // the number of bytes to encode the sequence. Instead of handling that specific range of lengths, 2402 // we handle all lengths inclusively between 0 and 255. 2403 cbor.split_first() 2404 .ok_or(AttestationErr::Len) 2405 .and_then(|(bytes, bytes_rem)| { 2406 if bytes & cbor::BYTES == cbor::BYTES { 2407 let len_info = bytes ^ cbor::BYTES; 2408 match len_info { 2409 ..=23 => Ok((bytes_rem, len_info)), 2410 24 => bytes_rem.split_first().ok_or(AttestationErr::Len).and_then( 2411 |(&len, len_rem)| { 2412 if len > 23 { 2413 Ok((len_rem, len)) 2414 } else { 2415 Err(AttestationErr::PackedFormatCborP256Signature) 2416 } 2417 }, 2418 ), 2419 _ => Err(AttestationErr::PackedFormatCborP256Signature), 2420 } 2421 .and_then(|(rem, len)| { 2422 rem.split_at_checked(usize::from(len)) 2423 .ok_or(AttestationErr::Len) 2424 .map(|(sig, remaining)| CborSuccess { 2425 value: Self(sig), 2426 remaining, 2427 }) 2428 }) 2429 } else { 2430 Err(AttestationErr::PackedFormatCborP256Signature) 2431 } 2432 }) 2433 } 2434 } 2435 /// An alleged DER-encoded P-384 signature using SHA-384. 2436 struct P384DerSig<'a>(&'a [u8]); 2437 impl<'a> FromCbor<'a> for P384DerSig<'a> { 2438 type Err = AttestationErr; 2439 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2440 // ```asn 2441 // Signature ::= SEQUENCE { 2442 // r INTEGER, 2443 // s INTEGER 2444 // } 2445 // ``` 2446 // We assume `r` and `s` can be _any_ 48-byte unsigned integer; thus when DER-encoded without metadata, 2447 // the number of bytes is inclusively between 1 and 49—INTEGER is a signed integer; thus a 0 byte must 2448 // be prepended iff the high bit is 1 (i.e., 48-byte unsigned integers with the high bit set take 49 bytes). 2449 // With metadata this makes the lengths inclusively between 3 and 51—we add 1 for the INTEGER tag and 1 2450 // for the number of bytes it takes to encode the integer. The total encoded length is thus inclusively 2451 // between 8 = 1 + 1 + 2(3) and 104 = 1 + 1 + 2(51)—we add 1 for the CONSTRUCTED SEQUENCE tag and 1 for 2452 // the number of bytes to encode the sequence. Instead of handling that specific range of lengths, 2453 // we handle all lengths inclusively between 0 and 255. 2454 cbor.split_first() 2455 .ok_or(AttestationErr::Len) 2456 .and_then(|(bytes, bytes_rem)| { 2457 if bytes & cbor::BYTES == cbor::BYTES { 2458 let len_info = bytes ^ cbor::BYTES; 2459 match len_info { 2460 ..=23 => Ok((bytes_rem, len_info)), 2461 24 => bytes_rem.split_first().ok_or(AttestationErr::Len).and_then( 2462 |(&len, len_rem)| { 2463 if len > 23 { 2464 Ok((len_rem, len)) 2465 } else { 2466 Err(AttestationErr::PackedFormatCborP384Signature) 2467 } 2468 }, 2469 ), 2470 _ => Err(AttestationErr::PackedFormatCborP384Signature), 2471 } 2472 .and_then(|(rem, len)| { 2473 rem.split_at_checked(usize::from(len)) 2474 .ok_or(AttestationErr::Len) 2475 .map(|(sig, remaining)| CborSuccess { 2476 value: Self(sig), 2477 remaining, 2478 }) 2479 }) 2480 } else { 2481 Err(AttestationErr::PackedFormatCborP384Signature) 2482 } 2483 }) 2484 } 2485 } 2486 /// An alleged RSASSA-PKCS1-v1_5 signature using SHA-256. 2487 struct RsaPkcs1v15Sig<'a>(&'a [u8]); 2488 impl<'a> FromCbor<'a> for RsaPkcs1v15Sig<'a> { 2489 type Err = AttestationErr; 2490 #[expect(clippy::big_endian_bytes, reason = "CBOR integers are big-endian")] 2491 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2492 // RSASSA-PKCS1-v1_5 signatures are the same length as the modulus. We only allow moduli consisting of 2493 // [`MIN_RSA_N_BYTES`] to [`MAX_RSA_N_BYTES`] bytes inclusively. This means 2494 // all signatures will use the same CBOR metadata tag (i.e., `cbor::BYTES_INFO_25`). 2495 cbor.split_first() 2496 .ok_or(AttestationErr::Len) 2497 .and_then(|(bytes, bytes_rem)| { 2498 if *bytes == cbor::BYTES_INFO_25 { 2499 bytes_rem 2500 .split_at_checked(2) 2501 .ok_or(AttestationErr::Len) 2502 .and_then(|(len_slice, len_rem)| { 2503 let mut cbor_len = [0; 2]; 2504 cbor_len.copy_from_slice(len_slice); 2505 // CBOR uints are big-endian. 2506 let len = usize::from(u16::from_be_bytes(cbor_len)); 2507 if len > 255 { 2508 len_rem 2509 .split_at_checked(len) 2510 .ok_or(AttestationErr::Len) 2511 .map(|(sig, remaining)| CborSuccess { 2512 value: Self(sig), 2513 remaining, 2514 }) 2515 } else { 2516 Err(AttestationErr::PackedFormatCborRs256Signature) 2517 } 2518 }) 2519 } else { 2520 Err(AttestationErr::PackedFormatCborRs256Signature) 2521 } 2522 }) 2523 } 2524 } 2525 /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation) signature. 2526 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2527 pub enum Sig<'a> { 2528 /// Alleged Ed25519 signature. 2529 Ed25519(Ed25519Signature<'a>), 2530 /// Alleged DER-encoded P-256 signature using SHA-256. 2531 P256(&'a [u8]), 2532 /// Alleged DER-encoded P-384 signature using SHA-384. 2533 P384(&'a [u8]), 2534 /// Alleged RSASSA-PKCS1-v1_5 signature using SHA-256. 2535 Rs256(&'a [u8]), 2536 } 2537 /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation). 2538 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2539 pub struct PackedAttestation<'a> { 2540 /// [Attestation signature](https://www.w3.org/TR/webauthn-3/#attestation-signature). 2541 pub signature: Sig<'a>, 2542 } 2543 impl<'a> FromCbor<'a> for PackedAttestation<'a> { 2544 type Err = AttestationErr; 2545 #[expect( 2546 clippy::too_many_lines, 2547 reason = "don't want to move code to an outer scope" 2548 )] 2549 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2550 /// Parses `data` as packed attestation up until `x5c` _without_ the `cbor::MAP_2` or `cbor::MAP_3` header. 2551 fn parse_to_cert_chain(data: &[u8]) -> Result<CborSuccess<'_, Sig<'_>>, AttestationErr> { 2552 // {"alg":CoseAlgorithmIdentifier,"sig":sig_bytes...} 2553 /// "alg" key. 2554 const ALG: [u8; 4] = [cbor::TEXT_3, b'a', b'l', b'g']; 2555 /// "sig" key. 2556 const SIG: [u8; 4] = [cbor::TEXT_3, b's', b'i', b'g']; 2557 data.split_at_checked(ALG.len()) 2558 .ok_or(AttestationErr::Len) 2559 .and_then(|(alg, alg_rem)| { 2560 if alg == ALG { 2561 alg_rem.split_first().ok_or(AttestationErr::Len).and_then( 2562 |(cose, cose_rem)| match *cose { 2563 EDDSA => cose_rem 2564 .split_at_checked(SIG.len()) 2565 .ok_or(AttestationErr::Len) 2566 .and_then(|(sig, sig_rem)| { 2567 if sig == SIG { 2568 Ed25519Signature::from_cbor(sig_rem).map(|success| { 2569 CborSuccess { 2570 value: Sig::Ed25519(success.value), 2571 remaining: success.remaining, 2572 } 2573 }) 2574 } else { 2575 Err(AttestationErr::PackedFormatMissingSig) 2576 } 2577 }), 2578 ES256 => cose_rem 2579 .split_at_checked(SIG.len()) 2580 .ok_or(AttestationErr::Len) 2581 .and_then(|(sig, sig_rem)| { 2582 if sig == SIG { 2583 P256DerSig::from_cbor(sig_rem).map(|success| { 2584 CborSuccess { 2585 value: Sig::P256(success.value.0), 2586 remaining: success.remaining, 2587 } 2588 }) 2589 } else { 2590 Err(AttestationErr::PackedFormatMissingSig) 2591 } 2592 }), 2593 cbor::NEG_INFO_24 => cose_rem 2594 .split_first() 2595 .ok_or(AttestationErr::Len) 2596 .and_then(|(len, len_rem)| { 2597 if *len == ES384 { 2598 len_rem 2599 .split_at_checked(SIG.len()) 2600 .ok_or(AttestationErr::Len) 2601 .and_then(|(sig, sig_rem)| { 2602 if sig == SIG { 2603 P384DerSig::from_cbor(sig_rem).map( 2604 |success| CborSuccess { 2605 value: Sig::P384(success.value.0), 2606 remaining: success.remaining, 2607 }, 2608 ) 2609 } else { 2610 Err(AttestationErr::PackedFormatMissingSig) 2611 } 2612 }) 2613 } else { 2614 Err(AttestationErr::PackedFormatUnsupportedAlg) 2615 } 2616 }), 2617 cbor::NEG_INFO_25 => cose_rem 2618 .split_at_checked(2) 2619 .ok_or(AttestationErr::Len) 2620 .and_then(|(len, len_rem)| { 2621 if len == RS256 { 2622 len_rem 2623 .split_at_checked(SIG.len()) 2624 .ok_or(AttestationErr::Len) 2625 .and_then(|(sig, sig_rem)| { 2626 if sig == SIG { 2627 RsaPkcs1v15Sig::from_cbor(sig_rem).map( 2628 |success| CborSuccess { 2629 value: Sig::Rs256(success.value.0), 2630 remaining: success.remaining, 2631 }, 2632 ) 2633 } else { 2634 Err(AttestationErr::PackedFormatMissingSig) 2635 } 2636 }) 2637 } else { 2638 Err(AttestationErr::PackedFormatUnsupportedAlg) 2639 } 2640 }), 2641 _ => Err(AttestationErr::PackedFormatUnsupportedAlg), 2642 }, 2643 ) 2644 } else { 2645 Err(AttestationErr::PackedFormatMissingAlg) 2646 } 2647 }) 2648 } 2649 cbor.split_first() 2650 .ok_or(AttestationErr::Len) 2651 .and_then(|(map, map_rem)| match *map { 2652 cbor::MAP_2 => parse_to_cert_chain(map_rem).map(|success| CborSuccess { 2653 value: Self { 2654 signature: success.value, 2655 }, 2656 remaining: success.remaining, 2657 }), 2658 _ => Err(AttestationErr::PackedFormat), 2659 }) 2660 } 2661 } 2662 /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids). 2663 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2664 pub enum AttestationFormat<'a> { 2665 /// [None](https://www.w3.org/TR/webauthn-3/#sctn-none-attestation). 2666 None, 2667 /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation). 2668 Packed(PackedAttestation<'a>), 2669 } 2670 impl<'a> FromCbor<'a> for AttestationFormat<'a> { 2671 type Err = AttestationErr; 2672 fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> { 2673 // Note we assume that cbor starts _after_ `cbor::MAP_3`. 2674 // {"fmt":"none"|"packed", "attStmt": NoneAttestation|CborPacked, ...}. 2675 /// "fmt" key. 2676 const FMT: [u8; 4] = [cbor::TEXT_3, b'f', b'm', b't']; 2677 /// "none" value. 2678 const NONE: [u8; 5] = [cbor::TEXT_4, b'n', b'o', b'n', b'e']; 2679 /// "packed" value. 2680 const PACKED: [u8; 7] = [cbor::TEXT_6, b'p', b'a', b'c', b'k', b'e', b'd']; 2681 /// "attStmt" key. 2682 const ATT_STMT: [u8; 8] = [cbor::TEXT_7, b'a', b't', b't', b'S', b't', b'm', b't']; 2683 cbor.split_at_checked(FMT.len()) 2684 .ok_or(AttestationErr::Len) 2685 .and_then(|(fmt, fmt_rem)| { 2686 if fmt == FMT { 2687 fmt_rem 2688 .split_at_checked(NONE.len()) 2689 .ok_or(AttestationErr::Len) 2690 // `PACKED.len() > NONE.len()`, so we check for `PACKED` in `and_then`. 2691 .and_then(|(none, none_rem)| { 2692 if none == NONE { 2693 none_rem 2694 .split_at_checked(ATT_STMT.len()) 2695 .ok_or(AttestationErr::Len) 2696 .and_then(|(att, att_rem)| { 2697 if att == ATT_STMT { 2698 NoneAttestation::from_cbor(att_rem).map(|success| { 2699 CborSuccess { 2700 value: Self::None, 2701 remaining: success.remaining, 2702 } 2703 }) 2704 } else { 2705 Err(AttestationErr::MissingStatement) 2706 } 2707 }) 2708 } else { 2709 fmt_rem 2710 .split_at_checked(PACKED.len()) 2711 .ok_or(AttestationErr::Len) 2712 .and_then(|(packed, packed_rem)| { 2713 if packed == PACKED { 2714 packed_rem 2715 .split_at_checked(ATT_STMT.len()) 2716 .ok_or(AttestationErr::Len) 2717 .and_then(|(att, att_rem)| { 2718 if att == ATT_STMT { 2719 PackedAttestation::from_cbor(att_rem).map( 2720 |success| CborSuccess { 2721 value: Self::Packed(success.value), 2722 remaining: success.remaining, 2723 }, 2724 ) 2725 } else { 2726 Err(AttestationErr::MissingStatement) 2727 } 2728 }) 2729 } else { 2730 Err(AttestationErr::UnsupportedFormat) 2731 } 2732 }) 2733 } 2734 }) 2735 } else { 2736 Err(AttestationErr::MissingFormat) 2737 } 2738 }) 2739 } 2740 } 2741 impl<'a> AttestationObject<'a> { 2742 /// Deserializes `data` based on the 2743 /// [attestation object layout](https://www.w3.org/TR/webauthn-3/#attestation-object) 2744 /// returning [`Self`] and the index within `data` that the authenticator data portion 2745 /// begins. 2746 #[expect(single_use_lifetimes, reason = "false positive")] 2747 #[expect( 2748 clippy::panic_in_result_fn, 2749 reason = "we want to crash when there is a bug" 2750 )] 2751 #[expect( 2752 clippy::arithmetic_side_effects, 2753 reason = "comment justifies its correctness" 2754 )] 2755 #[expect(clippy::big_endian_bytes, reason = "cbor lengths are in big-endian")] 2756 fn parse_data<'b: 'a>(data: &'b [u8]) -> Result<(Self, usize), AttestationObjectErr> { 2757 /// `authData` key. 2758 const AUTH_DATA_KEY: [u8; 9] = 2759 [cbor::TEXT_8, b'a', b'u', b't', b'h', b'D', b'a', b't', b'a']; 2760 // {"fmt":<AttestationFormat>, "attStmt":<AttestationObject>, "authData":<AuthentcatorData>}. 2761 data.split_first().ok_or(AttestationObjectErr::Len).and_then(|(map, map_rem)| { 2762 if *map == cbor::MAP_3 { 2763 AttestationFormat::from_cbor(map_rem).map_err(AttestationObjectErr::Attestation).and_then(|att| { 2764 att.remaining.split_at_checked(AUTH_DATA_KEY.len()).ok_or(AttestationObjectErr::Len).and_then(|(key, key_rem)| { 2765 if key == AUTH_DATA_KEY { 2766 key_rem.split_first().ok_or(AttestationObjectErr::Len).and_then(|(bytes, bytes_rem)| { 2767 if bytes & cbor::BYTES == cbor::BYTES { 2768 match bytes ^ cbor::BYTES { 2769 24 => bytes_rem.split_first().ok_or(AttestationObjectErr::Len).and_then(|(&len, auth_data)| { 2770 if len > 23 { 2771 Ok((usize::from(len), auth_data)) 2772 } else { 2773 Err(AttestationObjectErr::AuthDataLenInfo) 2774 } 2775 }), 2776 25 => bytes_rem.split_at_checked(2).ok_or(AttestationObjectErr::Len).and_then(|(len_slice, auth_data)| { 2777 let mut auth_len = [0; 2]; 2778 auth_len.copy_from_slice(len_slice); 2779 // Length is encoded as big-endian. 2780 let len = usize::from(u16::from_be_bytes(auth_len)); 2781 if len > 255 { 2782 Ok((len, auth_data)) 2783 } else { 2784 Err(AttestationObjectErr::AuthDataLenInfo) 2785 } 2786 }), 2787 _ => Err(AttestationObjectErr::AuthDataLenInfo), 2788 }.and_then(|(cbor_auth_data_len, auth_slice)| { 2789 if cbor_auth_data_len == auth_slice.len() { 2790 AuthenticatorData::from_cbor(auth_slice).map_err(|e| AttestationObjectErr::AuthData(e.into())).and_then(|auth_data| { 2791 assert!(auth_data.remaining.is_empty(), "there is a bug in AuthenticatorData::from_cbor"); 2792 match att.value { 2793 AttestationFormat::None => Ok(()), 2794 AttestationFormat::Packed(ref val) => match val.signature { 2795 Sig::Ed25519(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::Ed25519(_)) { 2796 Ok(()) 2797 } else { 2798 Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch) 2799 }, 2800 Sig::P256(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::P256(_)) { 2801 Ok(()) 2802 } else { 2803 Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch) 2804 }, 2805 Sig::P384(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::P384(_)) { 2806 Ok(()) 2807 } else { 2808 Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch) 2809 }, 2810 Sig::Rs256(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::Rsa(_)) { 2811 Ok(()) 2812 } else { 2813 Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch) 2814 }, 2815 }, 2816 // `cbor_auth_data_len == `auth_slice.len()` and `auth_slice.len() < data.len()`, so underflow won't happen. 2817 }.map(|()| (Self { attestation: att.value, auth_data: auth_data.value }, data.len() - cbor_auth_data_len)) 2818 }) 2819 } else { 2820 Err(AttestationObjectErr::CborAuthDataLenMismatch) 2821 } 2822 }) 2823 } else { 2824 Err(AttestationObjectErr::AuthDataType) 2825 } 2826 }) 2827 } else { 2828 Err(AttestationObjectErr::MissingAuthData) 2829 } 2830 }) 2831 }) 2832 } else { 2833 Err(AttestationObjectErr::NotAMapOf3) 2834 } 2835 }) 2836 } 2837 } 2838 /// [Attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object). 2839 #[derive(Debug)] 2840 pub struct AttestationObject<'a> { 2841 /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids). 2842 attestation: AttestationFormat<'a>, 2843 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data). 2844 auth_data: AuthenticatorData<'a>, 2845 } 2846 impl<'a> AttestationObject<'a> { 2847 /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids). 2848 #[inline] 2849 #[must_use] 2850 pub const fn attestation(&self) -> AttestationFormat<'a> { 2851 self.attestation 2852 } 2853 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data). 2854 #[inline] 2855 #[must_use] 2856 pub const fn auth_data(&self) -> &AuthenticatorData<'a> { 2857 &self.auth_data 2858 } 2859 } 2860 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for AttestationObject<'b> { 2861 type Error = AttestationObjectErr; 2862 /// Deserializes `value` based on the 2863 /// [attestation object layout](https://www.w3.org/TR/webauthn-3/#attestation-object). 2864 #[inline] 2865 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { 2866 Self::parse_data(value).map(|(val, _)| val) 2867 } 2868 } 2869 impl<'a> AuthDataContainer<'a> for AttestationObject<'a> { 2870 type Auth = AuthenticatorData<'a>; 2871 type Err = AttestationObjectErr; 2872 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 2873 #[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")] 2874 fn from_data(data: &'a [u8]) -> Result<ParsedAuthData<'a, Self>, Self::Err> { 2875 // `data.len().checked_sub(Sha256::output_size())` is clearly less than `data.len()`; 2876 // thus indexing wont `panic`. 2877 Self::parse_data(&data[..data.len().checked_sub(Sha256::output_size()).unwrap_or_else(|| unreachable!("AttestationObject::from_data must be passed a slice with 32 bytes of trailing data"))]).map(|(attest, auth_idx)| ParsedAuthData { data: attest, auth_data_and_32_trailing_bytes: &data[auth_idx..], }) 2878 } 2879 fn authenticator_data(&self) -> &Self::Auth { 2880 &self.auth_data 2881 } 2882 } 2883 /// [`AuthenticatorAttestationResponse`](https://www.w3.org/TR/webauthn-3/#authenticatorattestationresponse). 2884 #[derive(Debug)] 2885 pub struct AuthenticatorAttestation { 2886 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson). 2887 client_data_json: Vec<u8>, 2888 /// [attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object) followed by the SHA-256 hash 2889 /// of [`Self::client_data_json`]. 2890 attestation_object_and_c_data_hash: Vec<u8>, 2891 /// [`getTransports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-gettransports). 2892 transports: AuthTransports, 2893 } 2894 impl AuthenticatorAttestation { 2895 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson). 2896 #[inline] 2897 #[must_use] 2898 pub const fn client_data_json(&self) -> &[u8] { 2899 self.client_data_json.as_slice() 2900 } 2901 /// [attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object). 2902 #[expect( 2903 clippy::arithmetic_side_effects, 2904 clippy::indexing_slicing, 2905 reason = "comment justifies their correctness" 2906 )] 2907 #[inline] 2908 #[must_use] 2909 pub fn attestation_object(&self) -> &[u8] { 2910 // We only allow creation via [`Self::new`] which creates [`Self::attestation_object_and_c_data_hash`] 2911 // by appending the SHA-256 hash of [`Self::client_data_json`] to the attestation object that was passed; 2912 // thus indexing is fine and subtraction won't cause underflow. 2913 &self.attestation_object_and_c_data_hash 2914 [..self.attestation_object_and_c_data_hash.len() - Sha256::output_size()] 2915 } 2916 /// [`getTransports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-gettransports). 2917 #[inline] 2918 #[must_use] 2919 pub const fn transports(&self) -> AuthTransports { 2920 self.transports 2921 } 2922 /// Constructs an instance of `Self` with the contained data. 2923 /// 2924 /// Note calling code is encouraged to ensure `attestation_object` has at least 32 bytes 2925 /// of available capacity; if not, a reallocation will occur which may hinder performance 2926 /// depending on its size. 2927 #[inline] 2928 #[must_use] 2929 pub fn new( 2930 client_data_json: Vec<u8>, 2931 mut attestation_object: Vec<u8>, 2932 transports: AuthTransports, 2933 ) -> Self { 2934 attestation_object 2935 .extend_from_slice(Sha256::digest(client_data_json.as_slice()).as_slice()); 2936 Self { 2937 client_data_json, 2938 attestation_object_and_c_data_hash: attestation_object, 2939 transports, 2940 } 2941 } 2942 } 2943 impl AuthResponse for AuthenticatorAttestation { 2944 type Auth<'a> 2945 = AttestationObject<'a> 2946 where 2947 Self: 'a; 2948 type CredKey<'a> = (); 2949 #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")] 2950 fn parse_data_and_verify_sig( 2951 &self, 2952 (): Self::CredKey<'_>, 2953 relaxed: bool, 2954 ) -> Result< 2955 (CollectedClientData<'_>, Self::Auth<'_>), 2956 AuthRespErr<<Self::Auth<'_> as AuthDataContainer<'_>>::Err>, 2957 > { 2958 if relaxed { 2959 #[cfg(not(feature = "serde_relaxed"))] 2960 unreachable!("AuthenticatorAttestation::parse_data_and_verify_sig: must be passed false when serde_relaxed is not enabled"); 2961 #[cfg(feature = "serde_relaxed")] 2962 CollectedClientData::from_client_data_json_relaxed::<true>( 2963 self.client_data_json.as_slice(), 2964 ).map_err(AuthRespErr::CollectedClientDataRelaxed) 2965 } else { 2966 CollectedClientData::from_client_data_json::<true>(self.client_data_json.as_slice()).map_err(AuthRespErr::CollectedClientData) 2967 } 2968 .and_then(|client_data_json| { 2969 Self::Auth::from_data(self.attestation_object_and_c_data_hash.as_slice()) 2970 .map_err(AuthRespErr::Auth) 2971 .and_then(|val| { 2972 match val.data.auth_data.attested_credential_data.credential_public_key { 2973 UncompressedPubKey::Ed25519(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| { 2974 match val.data.attestation { 2975 AttestationFormat::None => Ok(()), 2976 AttestationFormat::Packed(packed) => match packed.signature { 2977 // We don't need to use `VerifyingKey::verify_strict` since 2978 // `Ed25519PubKey::into_ver_key` verifies the public key is not 2979 // in the small-order subgroup. `VerifyingKey::verify_strict` additionally 2980 // ensures _R_ of the signature is not in the small-order subgroup, but this 2981 // doesn't provide additional benefits and is still not enough to comply 2982 // with standards like RFC 8032 or NIST SP 800-186. 2983 Sig::Ed25519(sig) => ver_key.verify(val.auth_data_and_32_trailing_bytes, &sig.into_sig()).map_err(|_e| AuthRespErr::Signature), 2984 Sig::P256(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"), 2985 } 2986 } 2987 }), 2988 UncompressedPubKey::P256(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| { 2989 match val.data.attestation { 2990 AttestationFormat::None => Ok(()), 2991 AttestationFormat::Packed(packed) => match packed.signature { 2992 Sig::P256(sig) => P256Sig::from_bytes(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| ver_key.verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)), 2993 Sig::Ed25519(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"), 2994 } 2995 } 2996 }), 2997 UncompressedPubKey::P384(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| { 2998 match val.data.attestation { 2999 AttestationFormat::None => Ok(()), 3000 AttestationFormat::Packed(packed) => match packed.signature { 3001 Sig::P384(sig) => P384Sig::from_bytes(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| ver_key.verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)), 3002 Sig::Ed25519(_) | Sig::P256(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"), 3003 } 3004 } 3005 }), 3006 UncompressedPubKey::Rsa(key) => match val.data.attestation { 3007 AttestationFormat::None => Ok(()), 3008 AttestationFormat::Packed(packed) => match packed.signature { 3009 Sig::Rs256(sig) => pkcs1v15::Signature::try_from(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| key.as_ver_key().verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)), 3010 Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) => unreachable!("there is a bug in AttestationObject::from_data"), 3011 } 3012 }, 3013 }.map(|()| (client_data_json, val.data)) 3014 }) 3015 }) 3016 } 3017 } 3018 /// [`CredentialPropertiesOutput`](https://www.w3.org/TR/webauthn-3/#dictdef-credentialpropertiesoutput). 3019 /// 3020 /// Note [`Self::rk`] is frequently unreliable. For example there are times it is `Some(false)` despite the 3021 /// credential being stored client-side. One may have better luck checking if [`AuthTransports::contains`] 3022 /// [`AuthenticatorTransport::Internal`] and using that as an indicator if a client-side credential was created. 3023 #[derive(Clone, Copy, Debug)] 3024 pub struct CredentialPropertiesOutput { 3025 /// [`rk`](https://www.w3.org/TR/webauthn-3/#dom-credentialpropertiesoutput-rk). 3026 pub rk: Option<bool>, 3027 } 3028 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs). 3029 /// 3030 /// Note since this is a server-side library, we don't store 3031 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) 3032 /// since it contains sensitive data that should remain client-side. 3033 #[derive(Clone, Copy, Debug)] 3034 pub struct AuthenticationExtensionsPrfOutputs { 3035 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled). 3036 pub enabled: bool, 3037 } 3038 /// [`AuthenticationExtensionsClientOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputs). 3039 #[derive(Clone, Copy, Debug)] 3040 pub struct ClientExtensionsOutputs { 3041 /// [`credProps`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-credprops). 3042 pub cred_props: Option<CredentialPropertiesOutput>, 3043 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf). 3044 pub prf: Option<AuthenticationExtensionsPrfOutputs>, 3045 } 3046 /// [`ClientExtensionsOutputs`] extensions that are saved in [`Metadata`] because they are purely informative 3047 /// and not used during authentication ceremonies. 3048 #[derive(Clone, Copy, Debug)] 3049 pub struct ClientExtensionsOutputsMetadata { 3050 /// [`ClientExtensionsOutputs::cred_props`]. 3051 pub cred_props: Option<CredentialPropertiesOutput>, 3052 } 3053 /// [`ClientExtensionsOutputs`] extensions that are saved in [`StaticState`] because they are used during 3054 /// authentication ceremonies. 3055 #[derive(Clone, Copy, Debug)] 3056 pub struct ClientExtensionsOutputsStaticState { 3057 /// [`ClientExtensionsOutputs::prf`]. 3058 pub prf: Option<AuthenticationExtensionsPrfOutputs>, 3059 } 3060 impl From<ClientExtensionsOutputs> for ClientExtensionsOutputsMetadata { 3061 #[inline] 3062 fn from(value: ClientExtensionsOutputs) -> Self { 3063 Self { 3064 cred_props: value.cred_props, 3065 } 3066 } 3067 } 3068 impl From<ClientExtensionsOutputs> for ClientExtensionsOutputsStaticState { 3069 #[inline] 3070 fn from(value: ClientExtensionsOutputs) -> Self { 3071 Self { prf: value.prf } 3072 } 3073 } 3074 /// [`PublicKeyCredential`](https://www.w3.org/TR/webauthn-3/#iface-pkcredential) for registration ceremonies. 3075 #[expect( 3076 clippy::field_scoped_visibility_modifiers, 3077 reason = "no invariants to uphold" 3078 )] 3079 #[derive(Debug)] 3080 pub struct Registration { 3081 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response). 3082 pub(crate) response: AuthenticatorAttestation, 3083 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment). 3084 pub(crate) authenticator_attachment: AuthenticatorAttachment, 3085 /// [`getClientExtensionResults()`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults). 3086 pub(crate) client_extension_results: ClientExtensionsOutputs, 3087 } 3088 impl Registration { 3089 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response). 3090 #[inline] 3091 #[must_use] 3092 pub const fn response(&self) -> &AuthenticatorAttestation { 3093 &self.response 3094 } 3095 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment). 3096 #[inline] 3097 #[must_use] 3098 pub const fn authenticator_attachment(&self) -> AuthenticatorAttachment { 3099 self.authenticator_attachment 3100 } 3101 /// [`getClientExtensionResults()`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults). 3102 #[inline] 3103 #[must_use] 3104 pub const fn client_extension_results(&self) -> ClientExtensionsOutputs { 3105 self.client_extension_results 3106 } 3107 /// Constructs a `Registration` based on the passed arguments. 3108 #[cfg_attr(docsrs, doc(cfg(feature = "custom")))] 3109 #[cfg(feature = "custom")] 3110 #[inline] 3111 #[must_use] 3112 pub const fn new( 3113 response: AuthenticatorAttestation, 3114 authenticator_attachment: AuthenticatorAttachment, 3115 client_extension_results: ClientExtensionsOutputs, 3116 ) -> Self { 3117 Self { 3118 response, 3119 authenticator_attachment, 3120 client_extension_results, 3121 } 3122 } 3123 /// Returns the associated `SentChallenge`. 3124 /// 3125 /// This is useful when wanting to extract the corresponding [`RegistrationServerState`] from 3126 /// an in-memory collection (e.g., [`FixedCapHashSet`]) or storage. 3127 /// 3128 /// Note if [`CollectedClientData::from_client_data_json`] returns `Ok`, then this will return 3129 /// `Ok` containing the same value as [`CollectedClientData::challenge`]; however the converse 3130 /// is _not_ true. This is because this function parses the minimal amount of data possible. 3131 /// 3132 /// # Errors 3133 /// 3134 /// Errors iff [`AuthenticatorAttestation::client_data_json`] does not contain a base64url-encoded 3135 /// [`Challenge`] in the required position. 3136 #[inline] 3137 pub fn challenge(&self) -> Result<SentChallenge, CollectedClientDataErr> { 3138 LimitedVerificationParser::<true>::get_sent_challenge( 3139 self.response.client_data_json.as_slice(), 3140 ) 3141 } 3142 /// Returns the associated `SentChallenge`. 3143 /// 3144 /// This is useful when wanting to extract the corresponding [`RegistrationServerState`] from 3145 /// an in-memory collection (e.g., [`FixedCapHashSet`]) or storage. 3146 /// 3147 /// Note if [`CollectedClientData::from_client_data_json_relaxed`] returns `Ok`, then this will return 3148 /// `Ok` containing the same value as [`CollectedClientData::challenge`]; however the converse 3149 /// is _not_ true. This is because this function attempts to reduce the amount of data parsed. 3150 /// 3151 /// # Errors 3152 /// 3153 /// Errors iff [`AuthenticatorAttestation::client_data_json`] is invalid JSON _after_ ignoring 3154 /// a leading U+FEFF and replacing any sequences of invalid UTF-8 code units with U+FFFD or 3155 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge) does not exist 3156 /// or is not a base64url-encoded [`Challenge`]. 3157 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 3158 #[cfg(feature = "serde_relaxed")] 3159 #[inline] 3160 pub fn challenge_relaxed(&self) -> Result<SentChallenge, SerdeJsonErr> { 3161 RelaxedClientDataJsonParser::<true>::get_sent_challenge( 3162 self.response.client_data_json.as_slice(), 3163 ) 3164 } 3165 /// Convenience function for [`RegistrationRelaxed::deserialize`]. 3166 /// 3167 /// # Errors 3168 /// 3169 /// Errors iff [`RegistrationRelaxed::deserialize`] does. 3170 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 3171 #[cfg(feature = "serde_relaxed")] 3172 #[inline] 3173 pub fn from_json_relaxed(json: &[u8]) -> Result<Self, SerdeJsonErr> { 3174 serde_json::from_slice::<RegistrationRelaxed>(json).map(|val| val.0) 3175 } 3176 /// Convenience function for [`CustomRegistration::deserialize`]. 3177 /// 3178 /// # Errors 3179 /// 3180 /// Errors iff [`CustomRegistration::deserialize`] does. 3181 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 3182 #[cfg(feature = "serde_relaxed")] 3183 #[inline] 3184 pub fn from_json_custom(json: &[u8]) -> Result<Self, SerdeJsonErr> { 3185 serde_json::from_slice::<CustomRegistration>(json).map(|val| val.0) 3186 } 3187 } 3188 impl Response for Registration { 3189 type Auth = AuthenticatorAttestation; 3190 fn auth(&self) -> &Self::Auth { 3191 &self.response 3192 } 3193 } 3194 /// [Attestation statement](https://www.w3.org/TR/webauthn-3/#attestation-statement). 3195 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 3196 pub enum Attestation { 3197 /// [None](https://www.w3.org/TR/webauthn-3/#none). 3198 None, 3199 /// [Self](https://www.w3.org/TR/webauthn-3/#self). 3200 Surrogate, 3201 } 3202 /// Metadata associated with a [`RegisteredCredential`]. 3203 /// 3204 /// This information exists purely for informative reasons as it is not used in any way during authentication 3205 /// ceremonies; consequently, one may not want to store this information. 3206 #[derive(Clone, Copy, Debug)] 3207 pub struct Metadata<'a> { 3208 /// [Attestation statement](https://www.w3.org/TR/webauthn-3/#attestation-statement). 3209 pub attestation: Attestation, 3210 /// [`aaguid`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-aaguid). 3211 pub aaguid: Aaguid<'a>, 3212 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) output during registration that is 3213 /// never used during authentication ceremonies. 3214 pub extensions: AuthenticatorExtensionOutputMetadata, 3215 /// [`getClientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults) 3216 /// output during registration that is never used during authentication ceremonies. 3217 pub client_extension_results: ClientExtensionsOutputsMetadata, 3218 /// `ResidentKeyRequirement` sent during registration. 3219 pub resident_key: ResidentKeyRequirement, 3220 } 3221 impl Metadata<'_> { 3222 /// Transforms `self` into a JSON object conforming to the following pseudo-schema: 3223 /// 3224 /// ```json 3225 /// // MetadataJSON: 3226 /// { 3227 /// "attestation": "none" | "self", 3228 /// "aaguid": "<32-uppercase-hexadecimal digits>", 3229 /// "extensions": { 3230 /// "min_pin_length": null | <8-bit unsigned integer without leading 0s> 3231 /// }, 3232 /// "client_extension_results": { 3233 /// "cred_props": null | CredPropsJSON 3234 /// }, 3235 /// "resident_key": "required" | "discouraged" | "preferred" 3236 /// } 3237 /// // CredPropsJSON: 3238 /// { 3239 /// "rk": null | false | true 3240 /// } 3241 /// // PrfJSON: 3242 /// { 3243 /// "enabled": false | true 3244 /// } 3245 /// ``` 3246 /// where unnecessary whitespace does not exist. 3247 /// 3248 /// This primarily exists so that one can store the data in a human-readable way without the need of 3249 /// bringing the data back into the application. This allows one to read the data using some external 3250 /// means purely for informative reasons. If one wants the ability to "act" on the data; then one should 3251 /// [`Metadata::encode`] the data instead, or in addition to, that way it can be [`MetadataOwned::decode`]d. 3252 /// 3253 /// # Examples 3254 /// 3255 /// ``` 3256 /// # use core::str::FromStr; 3257 /// # use webauthn_rp::{ 3258 /// # request::register::{FourToSixtyThree, ResidentKeyRequirement}, 3259 /// # response::register::{ 3260 /// # Aaguid, Attestation, 3261 /// # AuthenticatorExtensionOutputMetadata, ClientExtensionsOutputsMetadata, CredentialPropertiesOutput, 3262 /// # Metadata, 3263 /// # }, 3264 /// # }; 3265 /// let metadata = Metadata { 3266 /// attestation: Attestation::None, 3267 /// aaguid: Aaguid::try_from([15; 16].as_slice())?, 3268 /// extensions: AuthenticatorExtensionOutputMetadata { 3269 /// min_pin_length: Some(FourToSixtyThree::Sixteen), 3270 /// }, 3271 /// client_extension_results: ClientExtensionsOutputsMetadata { 3272 /// cred_props: Some(CredentialPropertiesOutput { 3273 /// rk: Some(true), 3274 /// }), 3275 /// }, 3276 /// resident_key: ResidentKeyRequirement::Required 3277 /// }; 3278 /// let json = serde_json::json!({ 3279 /// "attestation": "none", 3280 /// "aaguid": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", 3281 /// "extensions": { 3282 /// "min_pin_length": 16 3283 /// }, 3284 /// "client_extension_results": { 3285 /// "cred_props": { 3286 /// "rk": true 3287 /// } 3288 /// }, 3289 /// "resident_key": "required" 3290 /// }); 3291 /// assert_eq!(metadata.into_json(), json.to_string()); 3292 /// # Ok::<_, webauthn_rp::AggErr>(()) 3293 /// ``` 3294 #[expect(unsafe_code, reason = "comment justifies its correctness and reason")] 3295 #[expect( 3296 clippy::arithmetic_side_effects, 3297 clippy::integer_division, 3298 clippy::integer_division_remainder_used, 3299 reason = "comments justify their correctness" 3300 )] 3301 #[inline] 3302 #[must_use] 3303 pub fn into_json(self) -> String { 3304 // Maximum capacity needed is not _that_ much larger than the minimum, 173. An example is the 3305 // following: 3306 // `{"attestation":"none","aaguid":"00000000000000000000000000000000","extensions":{"min_pin_length":null},"client_extension_results":{"cred_props":{"rk":false},"prf":{"enabled":false}},"resident_key":"discouraged"}`. 3307 // We use a raw `Vec` instead of a `String` since we need to transform some binary values into ASCII which 3308 // is easier to do as bytes. 3309 let mut buffer = Vec::with_capacity(187); 3310 buffer.extend_from_slice(br#"{"attestation":"#); 3311 buffer.extend_from_slice(match self.attestation { 3312 Attestation::None => br#""none","aaguid":""#, 3313 Attestation::Surrogate => br#""self","aaguid":""#, 3314 }); 3315 self.aaguid.0.iter().fold((), |(), byt| { 3316 // Get the first nibble. 3317 let nib_fst = byt & 0xf0; 3318 // We simply add the appropriate offset. For decimal digits this means simply adding `b'0'`; but 3319 // for uppercase hexadecimal, this means adding 55 since `b'A'` is 65. 3320 // Overflow cannot occur since this maxes at `b'F'`. 3321 buffer.push(nib_fst + if nib_fst < 0xa { b'0' } else { 55 }); 3322 // Get the second nibble. 3323 let nib_snd = byt & 0xf; 3324 // We simply add the appropriate offset. For decimal digits this means simply adding `b'0'`; but 3325 // for uppercase hexadecimal, this means adding 55 since `b'A'` is 65. 3326 // Overflow cannot occur since this maxes at `b'F'`. 3327 buffer.push(nib_snd + if nib_snd < 0xa { b'0' } else { 55 }); 3328 }); 3329 buffer.extend_from_slice(br#"","extensions":{"min_pin_length":"#); 3330 match self.extensions.min_pin_length { 3331 None => buffer.extend_from_slice(b"null"), 3332 Some(pin) => { 3333 // Clearly correct. 3334 let dig_1 = pin.into_u8() / 10; 3335 // Clearly correct. 3336 let dig_2 = pin.into_u8() % 10; 3337 if dig_1 > 0 { 3338 // We simply add the appropriate offset which is `b'0` for decimal digits. 3339 // Overflow cannot occur since this maxes at `b'9'`. 3340 buffer.push(dig_1 + b'0'); 3341 } 3342 // We simply add the appropriate offset which is `b'0` for decimal digits. 3343 // Overflow cannot occur since this maxes at `b'9'`. 3344 buffer.push(dig_2 + b'0'); 3345 } 3346 } 3347 buffer.extend_from_slice(br#"},"client_extension_results":{"cred_props":"#); 3348 match self.client_extension_results.cred_props { 3349 None => buffer.extend_from_slice(b"null"), 3350 Some(props) => { 3351 buffer.extend_from_slice(br#"{"rk":"#); 3352 match props.rk { 3353 None => buffer.extend_from_slice(b"null}"), 3354 Some(rk) => buffer.extend_from_slice(if rk { b"true}" } else { b"false}" }), 3355 } 3356 } 3357 } 3358 buffer.extend_from_slice(br#"},"resident_key":"#); 3359 buffer.extend_from_slice(match self.resident_key { 3360 ResidentKeyRequirement::Required => br#""required"}"#, 3361 ResidentKeyRequirement::Discouraged => br#""discouraged"}"#, 3362 ResidentKeyRequirement::Preferred => br#""preferred"}"#, 3363 }); 3364 // SAFETY: 3365 // Clearly above only appends ASCII, a subset of UTF-8, to `buffer`; thus `buffer` 3366 // is valid UTF-8. 3367 unsafe { String::from_utf8_unchecked(buffer) } 3368 } 3369 /// Transforms `self` into an "owned" version. 3370 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 3371 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 3372 #[cfg(feature = "bin")] 3373 #[inline] 3374 #[must_use] 3375 pub fn into_owned(self) -> MetadataOwned { 3376 MetadataOwned { 3377 attestation: self.attestation, 3378 aaguid: AaguidOwned(self.aaguid.0.try_into().unwrap_or_else(|_e| { 3379 unreachable!( 3380 "there is a bug in Metadata that allows AAGUID to not have length of 16" 3381 ) 3382 })), 3383 extensions: self.extensions, 3384 client_extension_results: self.client_extension_results, 3385 resident_key: self.resident_key, 3386 } 3387 } 3388 } 3389 /// [`RegisteredCredential`] and [`AuthenticatedCredential`] static state. 3390 /// 3391 /// `PublicKey` needs to be [`UncompressedPubKey`] or [`CompressedPubKey`] for this type to be of any use. 3392 #[derive(Clone, Copy, Debug)] 3393 pub struct StaticState<PublicKey> { 3394 /// [`credentialPublicKey`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialpublickey). 3395 pub credential_public_key: PublicKey, 3396 /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) output during registration that are 3397 /// used during authentication ceremonies. 3398 pub extensions: AuthenticatorExtensionOutputStaticState, 3399 /// [`getClientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults) 3400 /// output during registration that are used during authentication ceremonies. 3401 pub client_extension_results: ClientExtensionsOutputsStaticState, 3402 } 3403 impl<'a: 'b, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>, T3: AsRef<[u8]>, T4: AsRef<[u8]>> 3404 From<&'a StaticState<CompressedPubKey<T, T2, T3, T4>>> 3405 for StaticState<CompressedPubKey<&'b [u8], &'b [u8], &'b [u8], &'b [u8]>> 3406 { 3407 #[inline] 3408 fn from(value: &'a StaticState<CompressedPubKey<T, T2, T3, T4>>) -> Self { 3409 Self { 3410 credential_public_key: (&value.credential_public_key).into(), 3411 extensions: value.extensions, 3412 client_extension_results: value.client_extension_results, 3413 } 3414 } 3415 } 3416 /// `StaticState` with an uncompressed [`Self::credential_public_key`]. 3417 pub type StaticStateUncompressed<'a> = StaticState<UncompressedPubKey<'a>>; 3418 /// `StaticState` with a compressed [`Self::credential_public_key`] that owns the key data. 3419 pub type StaticStateCompressed = StaticState<CompressedPubKeyOwned>; 3420 impl StaticStateUncompressed<'_> { 3421 /// Transforms `self` into `StaticState` that contains the compressed version of the public key. 3422 #[inline] 3423 #[must_use] 3424 pub fn into_compressed(self) -> StaticStateCompressed { 3425 StaticStateCompressed { 3426 credential_public_key: self.credential_public_key.into_compressed(), 3427 extensions: self.extensions, 3428 client_extension_results: self.client_extension_results, 3429 } 3430 } 3431 } 3432 /// [`RegisteredCredential`] and [`AuthenticatedCredential`] dynamic state. 3433 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 3434 pub struct DynamicState { 3435 /// [`UV`](https://www.w3.org/TR/webauthn-3/#authdata-flags-uv). 3436 /// 3437 /// Once this is `true`, it will remain `true`. It will be set to `true` when `false` iff 3438 /// [`Flag::user_verified`] and [`AuthenticationVerificationOptions::update_uv`]. In other words, this only 3439 /// means that the user has been verified _at some point in the past_; but it does _not_ mean the user has 3440 /// most-recently been verified this is because user verification is a _ceremony-specific_ property. To 3441 /// enforce user verification for all ceremonies, [`UserVerificationRequirement::Required`] must always be 3442 /// sent. 3443 pub user_verified: bool, 3444 /// This can only be updated if [`BackupReq`] allows for it. 3445 pub backup: Backup, 3446 /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount). 3447 /// 3448 /// This is only updated if the authenticator supports 3449 /// [signature counters](https://www.w3.org/TR/webauthn-3/#signature-counter), and the behavior of how it is 3450 /// updated is controlled by [`AuthenticationVerificationOptions::sig_counter_enforcement`]. 3451 pub sign_count: u32, 3452 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment). 3453 /// 3454 /// [`AuthenticationVerificationOptions::auth_attachment_enforcement`] controls if/how this updated. 3455 pub authenticator_attachment: AuthenticatorAttachment, 3456 } 3457 impl PartialEq<&Self> for DynamicState { 3458 #[inline] 3459 fn eq(&self, other: &&Self) -> bool { 3460 *self == **other 3461 } 3462 } 3463 impl PartialEq<DynamicState> for &DynamicState { 3464 #[inline] 3465 fn eq(&self, other: &DynamicState) -> bool { 3466 **self == *other 3467 } 3468 } 3469 #[cfg(test)] 3470 mod tests { 3471 use super::{ 3472 super::{ 3473 super::{ 3474 AggErr, 3475 request::{AsciiDomain, RpId}, 3476 }, 3477 auth::{AuthenticatorData, NonDiscoverableAuthenticatorAssertion}, 3478 }, 3479 AttestationFormat, AttestationObject, AuthDataContainer as _, AuthExtOutput as _, 3480 AuthTransports, AuthenticatorAttestation, AuthenticatorExtensionOutput, 3481 AuthenticatorExtensionOutputErr, Backup, CborSuccess, CredentialProtectionPolicy, 3482 FourToSixtyThree, FromCbor as _, HmacSecret, Sig, UncompressedPubKey, 3483 cbor::{ 3484 BYTES, BYTES_INFO_24, MAP_1, MAP_2, MAP_3, MAP_4, SIMPLE_FALSE, SIMPLE_TRUE, TEXT_11, 3485 TEXT_12, TEXT_14, 3486 }, 3487 }; 3488 use ed25519_dalek::Verifier as _; 3489 use p256::ecdsa::{DerSignature as P256Sig, SigningKey as P256Key}; 3490 use rsa::sha2::{Digest as _, Sha256}; 3491 fn hex_decode<const N: usize>(input: &[u8; N]) -> Vec<u8> { 3492 /// Value to subtract from a lowercase hex digit. 3493 const LOWER_OFFSET: u8 = b'a' - 10; 3494 assert_eq!( 3495 N & 1, 3496 0, 3497 "hex_decode must be passed a reference to an array of even length" 3498 ); 3499 let mut data = Vec::with_capacity(N >> 1); 3500 input.chunks_exact(2).fold((), |(), byte| { 3501 let mut hex = byte[0]; 3502 let val = match hex { 3503 b'0'..=b'9' => hex - b'0', 3504 b'a'..=b'f' => hex - LOWER_OFFSET, 3505 _ => panic!("hex_decode must be passed a valid lowercase hexadecimal array"), 3506 } << 4; 3507 hex = byte[1]; 3508 data.push( 3509 val | match hex { 3510 b'0'..=b'9' => hex - b'0', 3511 b'a'..=b'f' => hex - LOWER_OFFSET, 3512 _ => panic!("hex_decode must be passed a valid lowercase hexadecimal array"), 3513 }, 3514 ); 3515 }); 3516 data 3517 } 3518 /// https://pr-preview.s3.amazonaws.com/w3c/webauthn/pull/2209.html#sctn-test-vectors-none-es256 3519 #[test] 3520 fn es256_test_vector() -> Result<(), AggErr> { 3521 let rp_id = RpId::Domain(AsciiDomain::try_from("example.org".to_owned())?); 3522 let credential_private_key = 3523 hex_decode(b"6e68e7a58484a3264f66b77f5d6dc5bc36a47085b615c9727ab334e8c369c2ee"); 3524 let aaguid = hex_decode(b"8446ccb9ab1db374750b2367ff6f3a1f"); 3525 let credential_id = 3526 hex_decode(b"f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4"); 3527 let client_data_json = hex_decode(b"7b2274797065223a22776562617574686e2e637265617465222c226368616c6c656e6765223a22414d4d507434557878475453746e63647134313759447742466938767049612d7077386f4f755657345441222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73652c22657874726144617461223a22636c69656e74446174614a534f4e206d617920626520657874656e6465642077697468206164646974696f6e616c206669656c647320696e20746865206675747572652c207375636820617320746869733a20426b5165446a646354427258426941774a544c4535513d3d227d"); 3528 let attestation_object = hex_decode(b"a363666d74646e6f6e656761747453746d74a068617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b559000000008446ccb9ab1db374750b2367ff6f3a1f0020f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4a5010203262001215820afefa16f97ca9b2d23eb86ccb64098d20db90856062eb249c33a9b672f26df61225820930a56b87a2fca66334b03458abf879717c12cc68ed73290af2e2664796b9220"); 3529 let key = *P256Key::from_slice(credential_private_key.as_slice()) 3530 .unwrap() 3531 .verifying_key(); 3532 let enc_key = key.to_encoded_point(false); 3533 let auth_attest = 3534 AuthenticatorAttestation::new(client_data_json, attestation_object, AuthTransports(0)); 3535 let att_obj = AttestationObject::from_data( 3536 auth_attest.attestation_object_and_c_data_hash.as_slice(), 3537 )?; 3538 assert_eq!( 3539 aaguid, 3540 att_obj.data.auth_data.attested_credential_data.aaguid.0 3541 ); 3542 assert_eq!( 3543 credential_id, 3544 att_obj 3545 .data 3546 .auth_data 3547 .attested_credential_data 3548 .credential_id 3549 .0 3550 ); 3551 assert!( 3552 matches!(att_obj.data.auth_data.attested_credential_data.credential_public_key, UncompressedPubKey::P256(pub_key) if enc_key.x().unwrap().as_slice() == pub_key.0 && enc_key.y().unwrap().as_slice() == pub_key.1) 3553 ); 3554 assert_eq!( 3555 att_obj.data.auth_data.rp_id_hash, 3556 Sha256::digest(rp_id.as_ref()).as_slice() 3557 ); 3558 assert!(att_obj.data.auth_data.flags.user_present); 3559 assert!(matches!(att_obj.data.attestation, AttestationFormat::None)); 3560 let authenticator_data = hex_decode( 3561 b"bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b51900000000", 3562 ); 3563 let client_data_json_2 = hex_decode(b"7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224f63446e55685158756c5455506f334a5558543049393770767a7a59425039745a63685879617630314167222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73657d"); 3564 let signature = hex_decode(b"3046022100f50a4e2e4409249c4a853ba361282f09841df4dd4547a13a87780218deffcd380221008480ac0f0b93538174f575bf11a1dd5d78c6e486013f937295ea13653e331e87"); 3565 let auth_assertion = NonDiscoverableAuthenticatorAssertion::<1>::without_user( 3566 client_data_json_2, 3567 authenticator_data, 3568 signature, 3569 ); 3570 let auth_data = AuthenticatorData::try_from(auth_assertion.authenticator_data())?; 3571 assert_eq!( 3572 auth_data.rp_id_hash(), 3573 Sha256::digest(rp_id.as_ref()).as_slice() 3574 ); 3575 assert!(auth_data.flags().user_present); 3576 assert!(match att_obj.data.auth_data.flags.backup { 3577 Backup::NotEligible => matches!(auth_data.flags().backup, Backup::NotEligible), 3578 Backup::Eligible => !matches!(auth_data.flags().backup, Backup::NotEligible), 3579 Backup::Exists => matches!(auth_data.flags().backup, Backup::Exists), 3580 }); 3581 let sig = P256Sig::from_bytes(auth_assertion.signature()).unwrap(); 3582 let mut msg = auth_assertion.authenticator_data().to_owned(); 3583 msg.extend_from_slice(Sha256::digest(auth_assertion.client_data_json()).as_slice()); 3584 assert!(key.verify(msg.as_slice(), &sig).is_ok()); 3585 Ok(()) 3586 } 3587 /// https://pr-preview.s3.amazonaws.com/w3c/webauthn/pull/2209.html#sctn-test-vectors-packed-self-es256 3588 #[test] 3589 fn es256_self_attest_test_vector() -> Result<(), AggErr> { 3590 let rp_id = RpId::Domain(AsciiDomain::try_from("example.org".to_owned())?); 3591 let credential_private_key = 3592 hex_decode(b"b4bbfa5d68e1693b6ef5a19a0e60ef7ee2cbcac81f7fec7006ac3a21e0c5116a"); 3593 let aaguid = hex_decode(b"df850e09db6afbdfab51697791506cfc"); 3594 let credential_id = 3595 hex_decode(b"455ef34e2043a87db3d4afeb39bbcb6cc32df9347c789a865ecdca129cbef58c"); 3596 let client_data_json = hex_decode(b"7b2274797065223a22776562617574686e2e637265617465222c226368616c6c656e6765223a2265476e4374334c55745936366b336a506a796e6962506b31716e666644616966715a774c33417032392d55222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73652c22657874726144617461223a22636c69656e74446174614a534f4e206d617920626520657874656e6465642077697468206164646974696f6e616c206669656c647320696e20746865206675747572652c207375636820617320746869733a205539685458764b453255526b4d6e625f3078594856673d3d227d"); 3597 let attestation_object = hex_decode(b"a363666d74667061636b65646761747453746d74a263616c67266373696758483046022100ae045923ded832b844cae4d5fc864277c0dc114ad713e271af0f0d371bd3ac540221009077a088ed51a673951ad3ba2673d5029bab65b64f4ea67b234321f86fcfac5d68617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b55d00000000df850e09db6afbdfab51697791506cfc0020455ef34e2043a87db3d4afeb39bbcb6cc32df9347c789a865ecdca129cbef58ca5010203262001215820eb151c8176b225cc651559fecf07af450fd85802046656b34c18f6cf193843c5225820927b8aa427a2be1b8834d233a2d34f61f13bfd44119c325d5896e183fee484f2"); 3598 let key = *P256Key::from_slice(credential_private_key.as_slice()) 3599 .unwrap() 3600 .verifying_key(); 3601 let enc_key = key.to_encoded_point(false); 3602 let auth_attest = 3603 AuthenticatorAttestation::new(client_data_json, attestation_object, AuthTransports(0)); 3604 let (att_obj, auth_idx) = AttestationObject::parse_data(auth_attest.attestation_object())?; 3605 assert_eq!(aaguid, att_obj.auth_data.attested_credential_data.aaguid.0); 3606 assert_eq!( 3607 credential_id, 3608 att_obj.auth_data.attested_credential_data.credential_id.0 3609 ); 3610 assert!( 3611 matches!(att_obj.auth_data.attested_credential_data.credential_public_key, UncompressedPubKey::P256(pub_key) if enc_key.x().unwrap().as_slice() == pub_key.0 && enc_key.y().unwrap().as_slice() == pub_key.1) 3612 ); 3613 assert_eq!( 3614 att_obj.auth_data.rp_id_hash, 3615 Sha256::digest(rp_id.as_ref()).as_slice() 3616 ); 3617 assert!(att_obj.auth_data.flags.user_present); 3618 assert!(match att_obj.attestation { 3619 AttestationFormat::None => false, 3620 AttestationFormat::Packed(attest) => { 3621 match attest.signature { 3622 Sig::Ed25519(_) | Sig::P384(_) | Sig::Rs256(_) => false, 3623 Sig::P256(sig) => { 3624 let s = P256Sig::from_bytes(sig).unwrap(); 3625 key.verify( 3626 &auth_attest.attestation_object_and_c_data_hash[auth_idx..], 3627 &s, 3628 ) 3629 .is_ok() 3630 } 3631 } 3632 } 3633 }); 3634 let authenticator_data = hex_decode( 3635 b"bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b50900000000", 3636 ); 3637 let client_data_json_2 = hex_decode(b"7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a225248696843784e534e493352594d45314f7731476d3132786e726b634a5f6666707637546e2d4a71386773222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73652c22657874726144617461223a22636c69656e74446174614a534f4e206d617920626520657874656e6465642077697468206164646974696f6e616c206669656c647320696e20746865206675747572652c207375636820617320746869733a206754623533727a36456853576f6d58477a696d4331513d3d227d"); 3638 let signature = hex_decode(b"3044022076691be76a8618976d9803c4cdc9b97d34a7af37e3bdc894a2bf54f040ffae850220448033a015296ffb09a762efd0d719a55346941e17e91ebf64c60d439d0b9744"); 3639 let auth_assertion = NonDiscoverableAuthenticatorAssertion::<1>::without_user( 3640 client_data_json_2, 3641 authenticator_data, 3642 signature, 3643 ); 3644 let auth_data = AuthenticatorData::try_from(auth_assertion.authenticator_data())?; 3645 assert_eq!( 3646 auth_data.rp_id_hash(), 3647 Sha256::digest(rp_id.as_ref()).as_slice() 3648 ); 3649 assert!(auth_data.flags().user_present); 3650 assert!(match att_obj.auth_data.flags.backup { 3651 Backup::NotEligible => matches!(auth_data.flags().backup, Backup::NotEligible), 3652 Backup::Eligible | Backup::Exists => 3653 !matches!(auth_data.flags().backup, Backup::NotEligible), 3654 }); 3655 let sig = P256Sig::from_bytes(auth_assertion.signature()).unwrap(); 3656 let mut msg = auth_assertion.authenticator_data().to_owned(); 3657 msg.extend_from_slice(Sha256::digest(auth_assertion.client_data_json()).as_slice()); 3658 assert!(key.verify(msg.as_slice(), &sig).is_ok()); 3659 Ok(()) 3660 } 3661 struct AuthExtOptions<'a> { 3662 cred_protect: Option<u8>, 3663 hmac_secret: Option<bool>, 3664 min_pin_length: Option<u8>, 3665 hmac_secret_mc: Option<&'a [u8]>, 3666 } 3667 fn generate_auth_extensions(opts: AuthExtOptions<'_>) -> Vec<u8> { 3668 let map_len = u8::from(opts.cred_protect.is_some()) 3669 + u8::from(opts.hmac_secret.is_some()) 3670 + u8::from(opts.min_pin_length.is_some()) 3671 + u8::from(opts.hmac_secret_mc.is_some()); 3672 let header = match map_len { 3673 0 => return Vec::new(), 3674 1 => MAP_1, 3675 2 => MAP_2, 3676 3 => MAP_3, 3677 4 => MAP_4, 3678 _ => unreachable!("bug"), 3679 }; 3680 let mut cbor = Vec::with_capacity(128); 3681 cbor.push(header); 3682 if let Some(protect) = opts.cred_protect { 3683 cbor.push(TEXT_11); 3684 cbor.extend_from_slice(b"credProtect".as_slice()); 3685 if protect >= 24 { 3686 cbor.push(24); 3687 } 3688 cbor.push(protect); 3689 } 3690 if let Some(hmac) = opts.hmac_secret { 3691 cbor.push(TEXT_11); 3692 cbor.extend_from_slice(b"hmac-secret".as_slice()); 3693 cbor.push(if hmac { SIMPLE_TRUE } else { SIMPLE_FALSE }); 3694 } 3695 if let Some(pin) = opts.min_pin_length { 3696 cbor.push(TEXT_12); 3697 cbor.extend_from_slice(b"minPinLength".as_slice()); 3698 if pin >= 24 { 3699 cbor.push(24); 3700 } 3701 cbor.push(pin); 3702 } 3703 if let Some(mc) = opts.hmac_secret_mc { 3704 cbor.push(TEXT_14); 3705 cbor.extend_from_slice(b"hmac-secret-mc".as_slice()); 3706 match mc.len() { 3707 len @ ..=23 => { 3708 cbor.push(BYTES | len as u8); 3709 } 3710 len @ 24..=255 => { 3711 cbor.push(BYTES_INFO_24); 3712 cbor.push(len as u8); 3713 } 3714 _ => panic!( 3715 "AuthExtOptions does not allow hmac_secret_mc to have length greater than 255" 3716 ), 3717 } 3718 cbor.extend_from_slice(mc); 3719 } 3720 cbor 3721 } 3722 #[test] 3723 fn test_auth_ext() -> Result<(), AuthenticatorExtensionOutputErr> { 3724 let opts = generate_auth_extensions(AuthExtOptions { 3725 cred_protect: None, 3726 hmac_secret: None, 3727 min_pin_length: None, 3728 hmac_secret_mc: None, 3729 }); 3730 let CborSuccess { value, remaining } = 3731 AuthenticatorExtensionOutput::from_cbor(opts.as_slice())?; 3732 assert!(remaining.is_empty()); 3733 assert!(value.missing()); 3734 let opts = generate_auth_extensions(AuthExtOptions { 3735 cred_protect: None, 3736 hmac_secret: None, 3737 min_pin_length: None, 3738 hmac_secret_mc: Some([0; 48].as_slice()), 3739 }); 3740 assert!( 3741 AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else( 3742 |e| matches!(e, AuthenticatorExtensionOutputErr::Missing), 3743 |_| false, 3744 ) 3745 ); 3746 let opts = generate_auth_extensions(AuthExtOptions { 3747 cred_protect: None, 3748 hmac_secret: Some(true), 3749 min_pin_length: None, 3750 hmac_secret_mc: Some([0; 48].as_slice()), 3751 }); 3752 let CborSuccess { value, remaining } = 3753 AuthenticatorExtensionOutput::from_cbor(opts.as_slice())?; 3754 assert!(remaining.is_empty()); 3755 assert!( 3756 matches!(value.cred_protect, CredentialProtectionPolicy::None) 3757 && matches!(value.hmac_secret, HmacSecret::One) 3758 && value.min_pin_length.is_none() 3759 ); 3760 let opts = generate_auth_extensions(AuthExtOptions { 3761 cred_protect: None, 3762 hmac_secret: Some(false), 3763 min_pin_length: None, 3764 hmac_secret_mc: Some([0; 48].as_slice()), 3765 }); 3766 assert!( 3767 AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else( 3768 |e| matches!(e, AuthenticatorExtensionOutputErr::Missing), 3769 |_| false, 3770 ) 3771 ); 3772 let opts = generate_auth_extensions(AuthExtOptions { 3773 cred_protect: None, 3774 hmac_secret: Some(true), 3775 min_pin_length: None, 3776 hmac_secret_mc: Some([0; 49].as_slice()), 3777 }); 3778 assert!( 3779 AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else( 3780 |e| matches!(e, AuthenticatorExtensionOutputErr::HmacSecretMcValue), 3781 |_| false, 3782 ) 3783 ); 3784 let opts = generate_auth_extensions(AuthExtOptions { 3785 cred_protect: None, 3786 hmac_secret: Some(true), 3787 min_pin_length: None, 3788 hmac_secret_mc: Some([0; 23].as_slice()), 3789 }); 3790 assert!( 3791 AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else( 3792 |e| matches!(e, AuthenticatorExtensionOutputErr::HmacSecretMcType), 3793 |_| false, 3794 ) 3795 ); 3796 let opts = generate_auth_extensions(AuthExtOptions { 3797 cred_protect: Some(1), 3798 hmac_secret: Some(true), 3799 min_pin_length: Some(5), 3800 hmac_secret_mc: Some([0; 48].as_slice()), 3801 }); 3802 let CborSuccess { value, remaining } = 3803 AuthenticatorExtensionOutput::from_cbor(opts.as_slice())?; 3804 assert!(remaining.is_empty()); 3805 assert!( 3806 matches!( 3807 value.cred_protect, 3808 CredentialProtectionPolicy::UserVerificationOptional 3809 ) && matches!(value.hmac_secret, HmacSecret::One) 3810 && value 3811 .min_pin_length 3812 .is_some_and(|pin| pin == FourToSixtyThree::Five) 3813 ); 3814 let opts = generate_auth_extensions(AuthExtOptions { 3815 cred_protect: Some(0), 3816 hmac_secret: None, 3817 min_pin_length: None, 3818 hmac_secret_mc: None, 3819 }); 3820 assert!( 3821 AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else( 3822 |e| matches!(e, AuthenticatorExtensionOutputErr::CredProtectValue), 3823 |_| false, 3824 ) 3825 ); 3826 let opts = generate_auth_extensions(AuthExtOptions { 3827 cred_protect: None, 3828 hmac_secret: None, 3829 min_pin_length: Some(3), 3830 hmac_secret_mc: None, 3831 }); 3832 assert!( 3833 AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else( 3834 |e| matches!(e, AuthenticatorExtensionOutputErr::MinPinLengthValue), 3835 |_| false, 3836 ) 3837 ); 3838 let opts = generate_auth_extensions(AuthExtOptions { 3839 cred_protect: None, 3840 hmac_secret: None, 3841 min_pin_length: Some(64), 3842 hmac_secret_mc: None, 3843 }); 3844 assert!( 3845 AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else( 3846 |e| matches!(e, AuthenticatorExtensionOutputErr::MinPinLengthValue), 3847 |_| false, 3848 ) 3849 ); 3850 Ok(()) 3851 } 3852 }