app_store_server_library/
chain_verifier.rs1use 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
35const APPLE_RECEIPT_SIGNER_OID: &str = "1.2.840.113635.100.6.11.1";
37const APPLE_WWDR_INTERMEDIATE_OID: &str = "1.2.840.113635.100.6.2.1";
39const 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
96const MAXIMUM_CACHE_SIZE: usize = 32;
98const 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
112pub struct ChainVerifier {
114 root_certificates: Vec<Vec<u8>>,
115 verified_public_key_cache: Mutex<HashMap<CacheKey, CacheValue>>,
116}
117
118impl ChainVerifier {
119 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 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 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 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}