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 {
184 bundle_id = None;
185 app_apple_id = None;
186 environment = None;
187 }
188
189 self.verify_notification_app_identifier_and_environment(bundle_id, app_apple_id, environment)?;
190
191 Ok(decoded_signed_notification)
192 }
193
194 fn verify_notification_app_identifier_and_environment(
195 &self,
196 bundle_id: Option<String>,
197 app_apple_id: Option<i64>,
198 environment: Option<Environment>,
199 ) -> Result<(), SignedDataVerifierError> {
200 if let Some(bundle_id) = bundle_id {
201 if bundle_id != self.bundle_id {
202 return Err(SignedDataVerifierError::InvalidAppIdentifier);
203 }
204 }
205
206 if self.environment == Environment::Production && self.app_apple_id != app_apple_id {
207 return Err(SignedDataVerifierError::InvalidAppIdentifier);
208 }
209
210 if let Some(environment) = environment {
211 if self.environment != Environment::LocalTesting && self.environment != environment {
212 return Err(SignedDataVerifierError::InvalidEnvironment);
213 }
214 }
215
216 Ok(())
217 }
218
219 pub fn verify_and_decode_app_transaction(
234 &self,
235 signed_app_transaction: &str,
236 ) -> Result<AppTransaction, SignedDataVerifierError> {
237 let decoded_app_transaction: AppTransaction = self.decode_signed_object(signed_app_transaction)?;
238
239 if decoded_app_transaction
240 .bundle_id
241 .as_ref()
242 != Some(&self.bundle_id)
243 {
244 return Err(SignedDataVerifierError::InvalidAppIdentifier);
245 }
246
247 if decoded_app_transaction
248 .receipt_type
249 .as_ref()
250 != Some(&self.environment)
251 {
252 return Err(SignedDataVerifierError::InvalidEnvironment);
253 }
254
255 Ok(decoded_app_transaction)
256 }
257
258 pub fn verify_and_decode_realtime_request(
273 &self,
274 signed_payload: &str,
275 ) -> Result<DecodedRealtimeRequestBody, SignedDataVerifierError> {
276 let decoded_realtime_request: DecodedRealtimeRequestBody = self.decode_signed_object(signed_payload)?;
277
278 if self.environment == Environment::Production && self.app_apple_id != Some(decoded_realtime_request.app_apple_id) {
279 return Err(SignedDataVerifierError::InvalidAppIdentifier);
280 }
281
282 if self.environment != decoded_realtime_request.environment {
283 return Err(SignedDataVerifierError::InvalidEnvironment);
284 }
285
286 Ok(decoded_realtime_request)
287 }
288
289 fn decode_signed_object<T: DeserializeOwned>(&self, signed_obj: &str) -> Result<T, SignedDataVerifierError> {
291 if self.environment == Environment::Xcode || self.environment == Environment::LocalTesting {
294 const EXPECTED_JWT_SEGMENTS: usize = 3;
295
296 let body_segments: Vec<&str> = signed_obj.split('.').collect();
297
298 if body_segments.len() != EXPECTED_JWT_SEGMENTS {
299 return Err(SignedDataVerifierError::VerificationFailure);
300 }
301
302 let _ = jsonwebtoken::decode_header(&signed_obj)?;
303 let body_base64 = base64_url_to_base64(body_segments[1]);
304 let body_data = STANDARD.decode(body_base64)?;
305 let decoded_body = serde_json::from_slice(&body_data)?;
306 return Ok(decoded_body);
307 }
308
309 let header = jsonwebtoken::decode_header(signed_obj)?;
310
311 let Some(x5c) = header.x5c else {
312 return Err(SignedDataVerifierError::VerificationFailure);
313 };
314
315 if x5c.is_empty() {
316 return Err(SignedDataVerifierError::VerificationFailure);
317 }
318
319 let x5c: Result<Vec<Vec<u8>>, DecodeError> = x5c
320 .iter()
321 .map(|c| c.as_der_bytes())
322 .collect();
323 let chain = x5c?;
324
325 if header.alg != Algorithm::ES256 {
326 return Err(SignedDataVerifierError::VerificationFailure);
327 }
328
329 let pub_key = self.verify_chain(&chain, None)?;
330 let pub_key = &pub_key[pub_key.len() - 65..];
331
332 let decoding_key = DecodingKey::from_ec_der(pub_key);
333 let claims: [&str; 0] = [];
334
335 let mut validator = Validation::new(Algorithm::ES256);
336 validator.validate_exp = false;
337 validator.set_required_spec_claims(&claims);
338
339 let payload = jsonwebtoken::decode::<T>(signed_obj, &decoding_key, &validator)?;
340 Ok(payload.claims)
341 }
342
343 fn verify_chain(&self, chain: &Vec<Vec<u8>>, effective_date: Option<u64>) -> Result<Vec<u8>, ChainVerifierError> {
344 if chain.len() != EXPECTED_CHAIN_LENGTH {
345 return Err(ChainVerifierError::VerificationFailure(InvalidChainLength))
346 }
347
348 let leaf = &chain[0];
349 let intermediate = &chain[1];
350
351 Ok(self.chain_verifier.verify(leaf, intermediate, effective_date)?)
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use crate::chain_verifier::ChainVerificationFailureReason::InvalidChainLength;
358 use super::*;
359
360 #[test]
361 fn test_invalid_chain_length() -> Result<(), ChainVerifierError> {
362 let root = Vec::new();
363 let leaf = Vec::new();
364 let intermediate = Vec::new();
365
366 let chain = vec![leaf, intermediate, Vec::new(), Vec::new()];
367 let verifier = SignedDataVerifier::new(vec![root], Environment::Production, "com.example".into(), Some(1234));
368 let public_key = verifier.verify_chain(&chain, None);
369
370 assert!(
371 matches!(
372 public_key.expect_err("Expect error"),
373 ChainVerifierError::VerificationFailure(InvalidChainLength)
374 )
375 );
376 Ok(())
377 }
378}