app_store_server_library/
signed_data_verifier.rs1use base64::engine::general_purpose::STANDARD;
2use base64::{DecodeError, Engine};
3
4use crate::chain_verifier::{ChainVerifier, ChainVerifierError};
5use crate::primitives::app_transaction::AppTransaction;
6use crate::primitives::environment::Environment;
7use crate::primitives::jws_renewal_info_decoded_payload::JWSRenewalInfoDecodedPayload;
8use crate::primitives::jws_transaction_decoded_payload::JWSTransactionDecodedPayload;
9use crate::primitives::response_body_v2_decoded_payload::ResponseBodyV2DecodedPayload;
10use crate::primitives::retention_messaging::decoded_realtime_request_body::DecodedRealtimeRequestBody;
11use crate::utils::{base64_url_to_base64, StringExt};
12use jsonwebtoken::{Algorithm, DecodingKey, Validation};
13use serde::de::DeserializeOwned;
14use crate::chain_verifier::ChainVerificationFailureReason::InvalidChainLength;
15
16#[derive(thiserror::Error, Debug)]
17pub enum SignedDataVerifierError {
18 #[error("VerificationFailure")]
19 VerificationFailure,
20
21 #[error("InvalidAppIdentifier")]
22 InvalidAppIdentifier,
23
24 #[error("InvalidEnvironment")]
25 InvalidEnvironment,
26
27 #[error("InternalChainVerifierError")]
28 InternalChainVerifierError(#[from] ChainVerifierError),
29
30 #[error("InternalDecodeError: [{0}]")]
31 InternalDecodeError(#[from] DecodeError),
32
33 #[error("InternalDeserializationError: [{0}]")]
34 InternalDeserializationError(#[from] serde_json::Error),
35
36 #[error("InternalJWTError: [{0}]")]
37 InternalJWTError(#[from] jsonwebtoken::errors::Error),
38}
39
40const EXPECTED_CHAIN_LENGTH: usize = 3;
41
42pub struct SignedDataVerifier {
45 environment: Environment,
46 bundle_id: String,
47 app_apple_id: Option<i64>,
48 chain_verifier: ChainVerifier,
49}
50
51impl SignedDataVerifier {
52 pub fn new(
65 root_certificates: Vec<Vec<u8>>,
66 environment: Environment,
67 bundle_id: String,
68 app_apple_id: Option<i64>,
69 ) -> Self {
70 let chain_verifier = ChainVerifier::new(root_certificates);
71
72 SignedDataVerifier {
73 environment,
74 bundle_id,
75 app_apple_id,
76 chain_verifier
77 }
78 }
79}
80
81impl SignedDataVerifier {
82 pub fn verify_and_decode_renewal_info(
97 &self,
98 signed_renewal_info: &str,
99 ) -> Result<JWSRenewalInfoDecodedPayload, SignedDataVerifierError> {
100 Ok(self.decode_signed_object(signed_renewal_info)?)
101 }
102
103 pub fn verify_and_decode_signed_transaction(
118 &self,
119 signed_transaction: &str,
120 ) -> Result<JWSTransactionDecodedPayload, SignedDataVerifierError> {
121 let decoded_signed_tx: JWSTransactionDecodedPayload = self.decode_signed_object(signed_transaction)?;
122
123 if decoded_signed_tx.bundle_id.as_ref() != Some(&self.bundle_id) {
124 return Err(SignedDataVerifierError::InvalidAppIdentifier);
125 }
126
127 if decoded_signed_tx.environment.as_ref() != Some(&self.environment) {
128 return Err(SignedDataVerifierError::InvalidEnvironment);
129 }
130
131 Ok(decoded_signed_tx)
132 }
133
134 pub fn verify_and_decode_notification(
149 &self,
150 signed_payload: &str,
151 ) -> Result<ResponseBodyV2DecodedPayload, SignedDataVerifierError> {
152 let decoded_signed_notification: ResponseBodyV2DecodedPayload = self.decode_signed_object(signed_payload)?;
153
154 let bundle_id;
155 let app_apple_id;
156 let environment;
157
158 if let Some(data) = &decoded_signed_notification.data {
159 bundle_id = data.bundle_id.clone();
160 app_apple_id = data.app_apple_id.clone();
161 environment = data.environment.clone();
162 } else if let Some(summary) = &decoded_signed_notification.summary {
163 bundle_id = summary.bundle_id.clone();
164 app_apple_id = summary.app_apple_id.clone();
165 environment = summary.environment.clone();
166 } else if let Some(external_purchase_token) = &decoded_signed_notification.external_purchase_token {
167 bundle_id = external_purchase_token
168 .bundle_id
169 .clone();
170 app_apple_id = external_purchase_token
171 .app_apple_id
172 .clone();
173
174 if let Some(external_purchase_id) = &external_purchase_token.external_purchase_id {
175 if external_purchase_id.starts_with("SANDBOX") {
176 environment = Some(Environment::Sandbox)
177 } else {
178 environment = Some(Environment::Production)
179 }
180 } else {
181 environment = Some(Environment::Production)
182 }
183 } else if let Some(app_data) = &decoded_signed_notification.app_data {
184 bundle_id = app_data.bundle_id.clone();
185 app_apple_id = app_data.app_apple_id.clone();
186 environment = app_data.environment.clone();
187 } else {
188 bundle_id = None;
189 app_apple_id = None;
190 environment = None;
191 }
192
193 self.verify_notification_app_identifier_and_environment(bundle_id, app_apple_id, environment)?;
194
195 Ok(decoded_signed_notification)
196 }
197
198 fn verify_notification_app_identifier_and_environment(
199 &self,
200 bundle_id: Option<String>,
201 app_apple_id: Option<i64>,
202 environment: Option<Environment>,
203 ) -> Result<(), SignedDataVerifierError> {
204 if let Some(bundle_id) = bundle_id {
205 if bundle_id != self.bundle_id {
206 return Err(SignedDataVerifierError::InvalidAppIdentifier);
207 }
208 }
209
210 if self.environment == Environment::Production && self.app_apple_id != app_apple_id {
211 return Err(SignedDataVerifierError::InvalidAppIdentifier);
212 }
213
214 if let Some(environment) = environment {
215 if self.environment != Environment::LocalTesting && self.environment != environment {
216 return Err(SignedDataVerifierError::InvalidEnvironment);
217 }
218 }
219
220 Ok(())
221 }
222
223 pub fn verify_and_decode_app_transaction(
238 &self,
239 signed_app_transaction: &str,
240 ) -> Result<AppTransaction, SignedDataVerifierError> {
241 let decoded_app_transaction: AppTransaction = self.decode_signed_object(signed_app_transaction)?;
242
243 if decoded_app_transaction
244 .bundle_id
245 .as_ref()
246 != Some(&self.bundle_id)
247 {
248 return Err(SignedDataVerifierError::InvalidAppIdentifier);
249 }
250
251 if decoded_app_transaction
252 .receipt_type
253 .as_ref()
254 != Some(&self.environment)
255 {
256 return Err(SignedDataVerifierError::InvalidEnvironment);
257 }
258
259 Ok(decoded_app_transaction)
260 }
261
262 pub fn verify_and_decode_realtime_request(
277 &self,
278 signed_payload: &str,
279 ) -> Result<DecodedRealtimeRequestBody, SignedDataVerifierError> {
280 let decoded_realtime_request: DecodedRealtimeRequestBody = self.decode_signed_object(signed_payload)?;
281
282 if self.environment == Environment::Production && self.app_apple_id != Some(decoded_realtime_request.app_apple_id) {
283 return Err(SignedDataVerifierError::InvalidAppIdentifier);
284 }
285
286 if self.environment != decoded_realtime_request.environment {
287 return Err(SignedDataVerifierError::InvalidEnvironment);
288 }
289
290 Ok(decoded_realtime_request)
291 }
292
293 fn decode_signed_object<T: DeserializeOwned>(&self, signed_obj: &str) -> Result<T, SignedDataVerifierError> {
295 if self.environment == Environment::Xcode || self.environment == Environment::LocalTesting {
298 const EXPECTED_JWT_SEGMENTS: usize = 3;
299
300 let body_segments: Vec<&str> = signed_obj.split('.').collect();
301
302 if body_segments.len() != EXPECTED_JWT_SEGMENTS {
303 return Err(SignedDataVerifierError::VerificationFailure);
304 }
305
306 let _ = jsonwebtoken::decode_header(&signed_obj)?;
307 let body_base64 = base64_url_to_base64(body_segments[1]);
308 let body_data = STANDARD.decode(body_base64)?;
309 let decoded_body = serde_json::from_slice(&body_data)?;
310 return Ok(decoded_body);
311 }
312
313 let header = jsonwebtoken::decode_header(signed_obj)?;
314
315 let Some(x5c) = header.x5c else {
316 return Err(SignedDataVerifierError::VerificationFailure);
317 };
318
319 if x5c.is_empty() {
320 return Err(SignedDataVerifierError::VerificationFailure);
321 }
322
323 let x5c: Result<Vec<Vec<u8>>, DecodeError> = x5c
324 .iter()
325 .map(|c| c.as_der_bytes())
326 .collect();
327 let chain = x5c?;
328
329 if header.alg != Algorithm::ES256 {
330 return Err(SignedDataVerifierError::VerificationFailure);
331 }
332
333 let pub_key = self.verify_chain(&chain, None)?;
334 let pub_key = &pub_key[pub_key.len() - 65..];
335
336 let decoding_key = DecodingKey::from_ec_der(pub_key);
337 let claims: [&str; 0] = [];
338
339 let mut validator = Validation::new(Algorithm::ES256);
340 validator.validate_exp = false;
341 validator.set_required_spec_claims(&claims);
342
343 let payload = jsonwebtoken::decode::<T>(signed_obj, &decoding_key, &validator)?;
344 Ok(payload.claims)
345 }
346
347 fn verify_chain(&self, chain: &Vec<Vec<u8>>, effective_date: Option<u64>) -> Result<Vec<u8>, ChainVerifierError> {
348 if chain.len() != EXPECTED_CHAIN_LENGTH {
349 return Err(ChainVerifierError::VerificationFailure(InvalidChainLength))
350 }
351
352 let leaf = &chain[0];
353 let intermediate = &chain[1];
354
355 Ok(self.chain_verifier.verify(leaf, intermediate, effective_date)?)
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use crate::chain_verifier::ChainVerificationFailureReason::InvalidChainLength;
362 use super::*;
363
364 #[test]
365 fn test_invalid_chain_length() -> Result<(), ChainVerifierError> {
366 let root = Vec::new();
367 let leaf = Vec::new();
368 let intermediate = Vec::new();
369
370 let chain = vec![leaf, intermediate, Vec::new(), Vec::new()];
371 let verifier = SignedDataVerifier::new(vec![root], Environment::Production, "com.example".into(), Some(1234));
372 let public_key = verifier.verify_chain(&chain, None);
373
374 assert!(
375 matches!(
376 public_key.expect_err("Expect error"),
377 ChainVerifierError::VerificationFailure(InvalidChainLength)
378 )
379 );
380 Ok(())
381 }
382}