Skip to main content

app_store_server_library/
chain_verifier.rs

1use std::collections::HashMap;
2use std::sync::Mutex;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use thiserror::Error;
6use x509_validator::rfc5280::RFC5280Policy;
7use x509_validator::store::CertificateStore;
8use x509_validator::unverified_chain::UnverifiedCertificateChain;
9use x509_validator::{Certificate, CertificateExt, Oid, PolicyEvaluationResult, PolicyFailureReason, ValidationPolicy};
10
11#[derive(Error, Debug, PartialEq)]
12pub enum ChainVerifierError {
13    #[error("VerificationFailure: [{0}]")]
14    VerificationFailure(ChainVerificationFailureReason),
15
16    #[error("InternalError: [{0}]")]
17    InternalError(String),
18
19    #[error("InternalDecodeError: [{0}]")]
20    InternalDecodeError(#[from] base64::DecodeError),
21}
22
23#[derive(Error, Debug, PartialEq)]
24pub enum ChainVerificationFailureReason {
25    #[error("InvalidAppIdentifier")]
26    InvalidAppIdentifier,
27
28    #[error("InvalidCertificate")]
29    InvalidCertificate,
30
31    #[error("InvalidChainLength")]
32    InvalidChainLength,
33}
34
35/// Apple's receipt-signing OID, expected on the leaf certificate.
36const APPLE_RECEIPT_SIGNER_OID: &str = "1.2.840.113635.100.6.11.1";
37/// Apple's WWDR OID, expected on the intermediate certificate.
38const APPLE_WWDR_INTERMEDIATE_OID: &str = "1.2.840.113635.100.6.2.1";
39/// leaf, intermediate, root.
40const EXPECTED_CHAIN_LENGTH: usize = 3;
41
42struct AppStoreOidPolicy {
43    wwdr_oid: Oid<'static>,
44    receipt_signer_oid: Oid<'static>,
45}
46
47impl AppStoreOidPolicy {
48    fn new() -> Self {
49        Self {
50            wwdr_oid: APPLE_WWDR_INTERMEDIATE_OID
51                .parse()
52                .expect("valid OID"),
53            receipt_signer_oid: APPLE_RECEIPT_SIGNER_OID
54                .parse()
55                .expect("valid OID"),
56        }
57    }
58
59    fn certificate_has_oid(certificate: &Certificate, oid: &Oid<'static>) -> bool {
60        certificate
61            .tbs_certificate
62            .iter_extensions()
63            .any(|ext| &ext.oid == oid)
64    }
65}
66
67impl ValidationPolicy for AppStoreOidPolicy {
68    fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
69        vec![]
70    }
71
72    fn chain_meets_policy_requirements(&self, chain: &UnverifiedCertificateChain) -> PolicyEvaluationResult {
73        if chain.len() != EXPECTED_CHAIN_LENGTH {
74            return Err(PolicyFailureReason::new("chain has unexpected length"));
75        }
76
77        let leaf = &chain[0];
78        let intermediate = &chain[1];
79
80        if !Self::certificate_has_oid(intermediate, &self.wwdr_oid) {
81            return Err(PolicyFailureReason::new(
82                "intermediate certificate does not contain WWDR OID",
83            ));
84        }
85
86        if !Self::certificate_has_oid(leaf, &self.receipt_signer_oid) {
87            return Err(PolicyFailureReason::new(
88                "leaf certificate does not contain Receipt Signing OID",
89            ));
90        }
91
92        Ok(())
93    }
94}
95
96/// There are unlikely to be more than a couple of keys at once.
97const MAXIMUM_CACHE_SIZE: usize = 32;
98/// 15 minutes, in seconds.
99const CACHE_TIME_LIMIT: u64 = 15 * 60;
100
101#[derive(PartialEq, Eq, Hash)]
102struct CacheKey {
103    leaf: Vec<u8>,
104    intermediate: Vec<u8>,
105}
106
107struct CacheValue {
108    expiration_time: u64,
109    public_key: Vec<u8>,
110}
111
112/// Verifies Apple's certificate chains.
113pub struct ChainVerifier {
114    root_certificates: Vec<Vec<u8>>,
115    verified_public_key_cache: Mutex<HashMap<CacheKey, CacheValue>>,
116}
117
118impl ChainVerifier {
119    /// Creates a verifier trusting the given DER-encoded root certificates.
120    pub fn new(root_certificates: Vec<Vec<u8>>) -> Self {
121        Self {
122            root_certificates,
123            verified_public_key_cache: Mutex::new(HashMap::new()),
124        }
125    }
126
127    /// Verifies the chain and returns the leaf's DER-encoded SubjectPublicKeyInfo.
128    ///
129    /// # Arguments
130    /// * `leaf` - DER-encoded leaf certificate
131    /// * `intermediate` - DER-encoded intermediate certificate
132    /// * `effective_date` - Optional Unix timestamp for validity checks
133    /// * `enable_online_checks` - Whether the verified public key is cached,
134    ///   matching the official Apple libraries. Revocation checking is not
135    ///   performed.
136    pub fn verify(
137        &self,
138        leaf: &[u8],
139        intermediate: &[u8],
140        effective_date: Option<u64>,
141        enable_online_checks: bool,
142    ) -> Result<Vec<u8>, ChainVerifierError> {
143        let now = SystemTime::now()
144            .duration_since(UNIX_EPOCH)
145            .map(|d| d.as_secs())
146            .unwrap_or(0);
147        self.verify_at(
148            leaf,
149            intermediate,
150            effective_date,
151            enable_online_checks,
152            now,
153        )
154    }
155
156    /// [`ChainVerifier::verify`], with the current time injected. Exposed for
157    /// deterministic testing of cache expiry.
158    pub fn verify_at(
159        &self,
160        leaf: &[u8],
161        intermediate: &[u8],
162        effective_date: Option<u64>,
163        enable_online_checks: bool,
164        now: u64,
165    ) -> Result<Vec<u8>, ChainVerifierError> {
166        if enable_online_checks {
167            if let Some(cached) = self.cached_public_key(leaf, intermediate, now) {
168                return Ok(cached);
169            }
170        }
171
172        let public_key = self.verify_without_caching(leaf, intermediate, effective_date)?;
173
174        if enable_online_checks {
175            self.store_public_key(leaf, intermediate, &public_key, now);
176        }
177
178        Ok(public_key)
179    }
180
181    /// Number of entries currently held. Exposed for testing.
182    pub fn cache_len(&self) -> usize {
183        self.verified_public_key_cache
184            .lock()
185            .map(|c| c.len())
186            .unwrap_or(0)
187    }
188
189    fn verify_without_caching(
190        &self,
191        leaf: &[u8],
192        intermediate: &[u8],
193        effective_date: Option<u64>,
194    ) -> Result<Vec<u8>, ChainVerifierError> {
195        let leaf = parse_certificate(leaf)?;
196        let intermediate = parse_certificate(intermediate)?;
197
198        let mut roots = CertificateStore::new();
199        for root_der in &self.root_certificates {
200            let root = parse_certificate(root_der)?;
201            roots.append(root);
202        }
203
204        let validation_time = effective_date
205            .map(|d| i64::try_from(d).unwrap_or(i64::MAX))
206            .unwrap_or_else(|| {
207                SystemTime::now()
208                    .duration_since(UNIX_EPOCH)
209                    .map(|d| d.as_secs() as i64)
210                    .unwrap_or(0)
211            });
212
213        let policy = x509_validator::policy! {
214            RFC5280Policy::new(validation_time);
215            AppStoreOidPolicy::new()
216        };
217
218        let validator = x509_validator::Validator::with_policy(roots, policy);
219
220        let mut intermediates = CertificateStore::new();
221        intermediates.append(intermediate);
222
223        match validator.validate_with_diagnostics(&leaf, &intermediates, &mut |_| {}) {
224            Ok(chain) => Ok(leaf_spki_der(chain.leaf())),
225            Err(_reasons) => Err(ChainVerifierError::VerificationFailure(
226                ChainVerificationFailureReason::InvalidCertificate,
227            )),
228        }
229    }
230
231    fn cached_public_key(&self, leaf: &[u8], intermediate: &[u8], now: u64) -> Option<Vec<u8>> {
232        let key = CacheKey {
233            leaf: leaf.to_vec(),
234            intermediate: intermediate.to_vec(),
235        };
236        let cache = self
237            .verified_public_key_cache
238            .lock()
239            .ok()?;
240        let value = cache.get(&key)?;
241        if value.expiration_time > now {
242            Some(value.public_key.clone())
243        } else {
244            None
245        }
246    }
247
248    fn store_public_key(&self, leaf: &[u8], intermediate: &[u8], public_key: &[u8], now: u64) {
249        let Ok(mut cache) = self.verified_public_key_cache.lock() else {
250            return;
251        };
252
253        cache.insert(
254            CacheKey {
255                leaf: leaf.to_vec(),
256                intermediate: intermediate.to_vec(),
257            },
258            CacheValue {
259                expiration_time: now + CACHE_TIME_LIMIT,
260                public_key: public_key.to_vec(),
261            },
262        );
263
264        if cache.len() > MAXIMUM_CACHE_SIZE {
265            cache.retain(|_, v| v.expiration_time > now);
266        }
267    }
268}
269
270fn parse_certificate(der: &[u8]) -> Result<Certificate<'_>, ChainVerifierError> {
271    Certificate::parse(der)
272        .map_err(|_| ChainVerifierError::VerificationFailure(ChainVerificationFailureReason::InvalidCertificate))
273}
274
275fn leaf_spki_der(leaf: &Certificate) -> Vec<u8> {
276    leaf.tbs_certificate
277        .subject_pki
278        .raw
279        .to_vec()
280}