fix(auth): reject malformed hmac-secret responses without panicking
- commit
- 2d0a9111e43ae54adda626587fe2fe202cb9d380
- parent
- fb504e0d73f1b74a55f5dc26d487e9c64da52e4f
- parent
- c725f1cebaca6911dd936530aa4634d9e61c0acf
- author
- FelineStateMachine <dami.etoile@gmail.com>
| M | src-tauri/ | 81 | ++++++++++++++++++++++++++++-- |
@@ -77,14 +77,24 @@ pub fn derive_secret_from_key(pin: &str) -> anyhow::Result<[u8; 32]> {
7777 }7878 7979 pub fn extract_hmac_secret(assertions: &[Assertion]) -> anyhow::Result<[u8; 32]> {80- for ext in &assertions[0].extensions {81- if let Gext::HmacSecret(Some(output)) = ext {82- let mut secret = [0u8; 32];83- secret.copy_from_slice(&output[..]);84- return Ok(secret);80+ let assertion = assertions.first().context(81+ "Security key returned no assertions. Reconnect the key and try the PIN and tap again.",82+ )?;83+84+ for ext in &assertion.extensions {85+ match ext {86+ Gext::HmacSecret(Some(output)) => return Ok(*output),87+ Gext::HmacSecret2(Some(_)) => {88+ anyhow::bail!(89+ "Security key returned a two-output hmac-secret response (64 bytes); Portal requires exactly one 32-byte output. Reconnect the key or use a compatible FIDO2 key."90+ )91+ }92+ _ => {}8593 }8694 }87- anyhow::bail!("No hmac-secret in assertion response")95+ anyhow::bail!(96+ "Security key assertion did not contain hmac-secret output. Use a compatible FIDO2 key and try again."97+ )8898 }8999 90100 pub fn derive_iroh_secret_from_key(pin: &str) -> anyhow::Result<SecretKey> {@@ -245,6 +255,65 @@ pub async fn key_derive_identity(pin: String) -> KeyIdentity {
245255 mod tests {246256 use super::*;247257 258+ 259+ fn extract_hmac_secret_rejects_empty_assertion_list() {260+ let error = extract_hmac_secret(&[]).expect_err("empty assertions must fail closed");261+ assert_eq!(262+ error.to_string(),263+ "Security key returned no assertions. Reconnect the key and try the PIN and tap again."264+ );265+ }266+267+ 268+ fn extract_hmac_secret_rejects_two_output_response() {269+ let assertion = Assertion {270+ extensions: vec![Gext::HmacSecret2(Some(([0x2a; 32], [0x2b; 32])))],271+ ..Default::default()272+ };273+274+ let error =275+ extract_hmac_secret(&[assertion]).expect_err("two-output response must fail closed");276+ assert_eq!(277+ error.to_string(),278+ "Security key returned a two-output hmac-secret response (64 bytes); Portal requires exactly one 32-byte output. Reconnect the key or use a compatible FIDO2 key."279+ );280+ }281+282+ 283+ fn extract_hmac_secret_rejects_extension_without_output() {284+ let assertion = Assertion {285+ extensions: vec![Gext::HmacSecret(None)],286+ ..Default::default()287+ };288+289+ let error = extract_hmac_secret(&[assertion]).expect_err("absent output must fail closed");290+ assert_eq!(291+ error.to_string(),292+ "Security key assertion did not contain hmac-secret output. Use a compatible FIDO2 key and try again."293+ );294+ }295+296+ 297+ fn extract_hmac_secret_rejects_assertion_without_extension() {298+ let error = extract_hmac_secret(&[Assertion::default()])299+ .expect_err("missing extension must fail closed");300+ assert_eq!(301+ error.to_string(),302+ "Security key assertion did not contain hmac-secret output. Use a compatible FIDO2 key and try again."303+ );304+ }305+306+ 307+ fn extract_hmac_secret_returns_exact_output() {308+ let expected = [0x2a; 32];309+ let assertion = Assertion {310+ extensions: vec![Gext::HmacSecret(Some(expected))],311+ ..Default::default()312+ };313+314+ assert_eq!(extract_hmac_secret(&[assertion]).unwrap(), expected);315+ }316+248317 249318 fn portal_identity_derivation_is_stable_and_domain_separated() {250319 let root = [0x2a; 32];