From 421912de3608cc4ab36817bc79356b6199d3b52f Mon Sep 17 00:00:00 2001 From: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Date: Fri, 31 Jul 2026 09:48:51 -0700 Subject: [PATCH] refactor(push): require validated App Attest bypass token Co-authored-by: Tom Brow Signed-off-by: Tom Brow --- crates/buzz-push-gateway/src/app_attest.rs | 8 ++++ .../src/app_attest_policy.rs | 42 ++++++++++++++++--- crates/buzz-push-gateway/src/config.rs | 38 +++++++++++++++++ crates/buzz-push-gateway/src/main.rs | 20 ++++----- 4 files changed, 93 insertions(+), 15 deletions(-) diff --git a/crates/buzz-push-gateway/src/app_attest.rs b/crates/buzz-push-gateway/src/app_attest.rs index ebb1fc56bc..27152600ad 100644 --- a/crates/buzz-push-gateway/src/app_attest.rs +++ b/crates/buzz-push-gateway/src/app_attest.rs @@ -45,6 +45,14 @@ impl AppAttestVerifier { apple_root_cert_pem, }) } + #[cfg(all(test, feature = "dev-app-attest-bypass"))] + pub(crate) fn for_policy_test() -> Self { + Self { + app_id: "policy-test".to_owned(), + apple_root_cert_pem: Vec::new(), + } + } + /// `client_data` is the exact canonical enrollment transcript represented by /// the challenge string passed to `attestKey`; callers must include every /// authority-bearing enrollment field in it. diff --git a/crates/buzz-push-gateway/src/app_attest_policy.rs b/crates/buzz-push-gateway/src/app_attest_policy.rs index 3ea5796075..a13cb83d94 100644 --- a/crates/buzz-push-gateway/src/app_attest_policy.rs +++ b/crates/buzz-push-gateway/src/app_attest_policy.rs @@ -3,6 +3,23 @@ use crate::app_attest::{ AppAttestError, AppAttestVerifier, VerifiedAssertion, VerifiedAttestation, }; +#[cfg(feature = "dev-app-attest-bypass")] +#[derive(Clone)] +pub struct DevelopmentAppAttestPolicy { + _private: (), +} + +#[cfg(feature = "dev-app-attest-bypass")] +pub struct DevelopmentAppAttestBypass { + _private: (), +} + +#[cfg(feature = "dev-app-attest-bypass")] +impl DevelopmentAppAttestBypass { + pub(crate) fn enabled() -> Self { + Self { _private: () } + } +} #[cfg_attr( not(feature = "dev-app-attest-bypass"), @@ -20,7 +37,7 @@ let _ = AppAttestPolicy::Development; pub enum AppAttestPolicy { Apple(AppAttestVerifier), #[cfg(feature = "dev-app-attest-bypass")] - Development, + Development(DevelopmentAppAttestPolicy), } impl AppAttestPolicy { @@ -29,8 +46,23 @@ impl AppAttestPolicy { } #[cfg(feature = "dev-app-attest-bypass")] - pub fn development() -> Self { - Self::Development + pub fn from_config( + bypass: Option, + apple: AppAttestVerifier, + ) -> Self { + if bypass.is_some() { + tracing::warn!( + "DEVELOPMENT APP ATTEST BYPASS ACTIVE; Apple attestation and assertion verification are disabled" + ); + Self::development() + } else { + Self::apple(apple) + } + } + + #[cfg(feature = "dev-app-attest-bypass")] + fn development() -> Self { + Self::Development(DevelopmentAppAttestPolicy { _private: () }) } pub fn verify_attestation( @@ -44,7 +76,7 @@ impl AppAttestPolicy { verifier.verify_attestation(attestation_b64, key_id_b64, client_data) } #[cfg(feature = "dev-app-attest-bypass")] - Self::Development => { + Self::Development(_) => { crate::dev_app_attest::verify_attestation(attestation_b64, key_id_b64, client_data) } } @@ -69,7 +101,7 @@ impl AppAttestPolicy { stored_challenge, ), #[cfg(feature = "dev-app-attest-bypass")] - Self::Development => crate::dev_app_attest::verify_assertion( + Self::Development(_) => crate::dev_app_attest::verify_assertion( assertion_b64, client_data, public_key, diff --git a/crates/buzz-push-gateway/src/config.rs b/crates/buzz-push-gateway/src/config.rs index 1cafd0acd8..5ca3b39983 100644 --- a/crates/buzz-push-gateway/src/config.rs +++ b/crates/buzz-push-gateway/src/config.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "dev-app-attest-bypass")] +use crate::app_attest_policy::DevelopmentAppAttestBypass; use base64::{engine::general_purpose::STANDARD, Engine as _}; use std::{ collections::{HashMap, HashSet}, @@ -76,6 +78,12 @@ fn parse_keyring( Ok(keys) } impl Config { + #[cfg(feature = "dev-app-attest-bypass")] + pub fn dev_app_attest_bypass(&self) -> Option { + self.dev_app_attest_bypass + .then(DevelopmentAppAttestBypass::enabled) + } + pub fn from_env() -> Result { Self::from_map(&std::env::vars().collect()) } @@ -206,6 +214,8 @@ impl Config { #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "dev-app-attest-bypass")] + use crate::app_attest_policy::AppAttestPolicy; fn base() -> HashMap { HashMap::from([ @@ -442,6 +452,34 @@ mod tests { assert!(Config::from_map(&env).unwrap().dev_app_attest_bypass); } + #[cfg(feature = "dev-app-attest-bypass")] + #[test] + fn dev_app_attest_bypass_selects_development_policy_from_validated_config() { + let mut env = base(); + env.insert("BUZZ_PUSH_DEV_APP_ATTEST_BYPASS".into(), "true".into()); + env.insert( + "BUZZ_PUSH_ENABLED_PROFILES".into(), + "buzz-ios-sandbox".into(), + ); + let config = Config::from_map(&env).unwrap(); + let apple = crate::app_attest::AppAttestVerifier::for_policy_test(); + + let policy = AppAttestPolicy::from_config(config.dev_app_attest_bypass(), apple); + + assert!(matches!(policy, AppAttestPolicy::Development(_))); + } + + #[cfg(feature = "dev-app-attest-bypass")] + #[test] + fn disabled_bypass_selects_apple_policy() { + let config = Config::from_map(&base()).unwrap(); + let apple = crate::app_attest::AppAttestVerifier::for_policy_test(); + + let policy = AppAttestPolicy::from_config(config.dev_app_attest_bypass(), apple); + + assert!(matches!(policy, AppAttestPolicy::Apple(_))); + } + #[test] fn malformed_or_empty_keyrings_fail_startup() { for (variable, value) in [ diff --git a/crates/buzz-push-gateway/src/main.rs b/crates/buzz-push-gateway/src/main.rs index e4d0630c9c..fd2acf859c 100644 --- a/crates/buzz-push-gateway/src/main.rs +++ b/crates/buzz-push-gateway/src/main.rs @@ -35,6 +35,8 @@ async fn main() -> Result<(), Box> { return Ok(()); } let c = Config::from_env()?; + #[cfg(feature = "dev-app-attest-bypass")] + let dev_app_attest_bypass = c.dev_app_attest_bypass(); let metrics_handle = buzz_push_gateway::metrics::install()?; let transport = Arc::new(ApnsTransport::certificate( &fs::read(&c.apns_cert_path)?, @@ -76,17 +78,15 @@ async fn main() -> Result<(), Box> { } } }); - let apple_app_attest = - AppAttestVerifier::new(c.app_attest_app_id, fs::read(&c.app_attest_root_cert_path)?)?; + let apple_app_attest = AppAttestVerifier::new( + c.app_attest_app_id.clone(), + fs::read(&c.app_attest_root_cert_path)?, + )?; #[cfg(feature = "dev-app-attest-bypass")] - let app_attest = if c.dev_app_attest_bypass { - tracing::warn!( - "DEVELOPMENT APP ATTEST BYPASS ACTIVE; Apple attestation and assertion verification are disabled" - ); - Arc::new(AppAttestPolicy::development()) - } else { - Arc::new(AppAttestPolicy::apple(apple_app_attest)) - }; + let app_attest = Arc::new(AppAttestPolicy::from_config( + dev_app_attest_bypass, + apple_app_attest, + )); #[cfg(not(feature = "dev-app-attest-bypass"))] let app_attest = Arc::new(AppAttestPolicy::apple(apple_app_attest)); let accepting = Arc::new(AtomicBool::new(true));