refactor(push): require validated App Attest bypass token

Co-authored-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp
2026-07-31 09:48:51 -07:00
committed by npub12wpjffj7q5qjsky5jvk4ldwlxmse5xll3d8gytk4wqd0c5y7jvwspg37n6
parent 5d236db269
commit 421912de36
4 changed files with 93 additions and 15 deletions

View File

@@ -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.

View File

@@ -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<DevelopmentAppAttestBypass>,
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,

View File

@@ -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<DevelopmentAppAttestBypass> {
self.dev_app_attest_bypass
.then(DevelopmentAppAttestBypass::enabled)
}
pub fn from_env() -> Result<Self, ConfigError> {
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<String, String> {
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 [

View File

@@ -35,6 +35,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
}
}
});
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));