app_store_server_library/
signed_data_verifier.rs1use base64::engine::general_purpose::STANDARD;
2use base64::{DecodeError, Engine};
3
4use crate::chain_verifier::{verify_chain, 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::utils::{base64_url_to_base64, StringExt};
11use jsonwebtoken::{Algorithm, DecodingKey, Validation};
12use serde::de::DeserializeOwned;
13
14#[derive(thiserror::Error, Debug, PartialEq)]
15pub enum SignedDataVerifierError {
16 #[error("VerificationFailure")]
17 VerificationFailure,
18
19 #[error("InvalidAppIdentifier")]
20 InvalidAppIdentifier,
21
22 #[error("InvalidEnvironment")]
23 InvalidEnvironment,
24
25 #[error("InternalChainVerifierError")]
26 InternalChainVerifierError(#[from] ChainVerifierError),
27
28 #[error("InternalDecodeError: [{0}]")]
29 InternalDecodeError(#[from] DecodeError),
30
31 #[error("InternalJWTError: [{0}]")]
32 InternalJWTError(#[from] jsonwebtoken::errors::Error),
33}
34
35pub struct SignedDataVerifier {
38 root_certificates: Vec<Vec<u8>>,
39 environment: Environment,
40 bundle_id: String,
41 app_apple_id: Option<i64>,
42}
43
44impl SignedDataVerifier {
45 pub fn new(
58 root_certificates: Vec<Vec<u8>>,
59 environment: Environment,
60 bundle_id: String,
61 app_apple_id: Option<i64>,
62 ) -> Self {
63 return SignedDataVerifier {
64 root_certificates,
65 environment,
66 bundle_id,
67 app_apple_id,
68 };
69 }
70}
71
72impl SignedDataVerifier {
73 pub fn verify_and_decode_renewal_info(
88 &self,
89 signed_renewal_info: &str,
90 ) -> Result<JWSRenewalInfoDecodedPayload, SignedDataVerifierError> {
91 Ok(self.decode_signed_object(signed_renewal_info)?)
92 }
93
94 pub fn verify_and_decode_signed_transaction(
109 &self,
110 signed_transaction: &str,
111 ) -> Result<JWSTransactionDecodedPayload, SignedDataVerifierError> {
112 let decoded_signed_tx: JWSTransactionDecodedPayload = self.decode_signed_object(signed_transaction)?;
113
114 if decoded_signed_tx.bundle_id.as_ref() != Some(&self.bundle_id) {
115 return Err(SignedDataVerifierError::InvalidAppIdentifier);
116 }
117
118 if decoded_signed_tx.environment.as_ref() != Some(&self.environment) {
119 return Err(SignedDataVerifierError::InvalidEnvironment);
120 }
121
122 Ok(decoded_signed_tx)
123 }
124
125 pub fn verify_and_decode_notification(
140 &self,
141 signed_payload: &str,
142 ) -> Result<ResponseBodyV2DecodedPayload, SignedDataVerifierError> {
143 let decoded_signed_notification: ResponseBodyV2DecodedPayload = self.decode_signed_object(signed_payload)?;
144
145 let bundle_id;
146 let app_apple_id;
147 let environment;
148
149 if let Some(data) = &decoded_signed_notification.data {
150 bundle_id = data.bundle_id.clone();
151 app_apple_id = data.app_apple_id.clone();
152 environment = data.environment.clone();
153 } else if let Some(summary) = &decoded_signed_notification.summary {
154 bundle_id = summary.bundle_id.clone();
155 app_apple_id = summary.app_apple_id.clone();
156 environment = summary.environment.clone();
157 } else if let Some(external_purchase_token) = &decoded_signed_notification.external_purchase_token {
158 bundle_id = external_purchase_token.bundle_id.clone();
159 app_apple_id = external_purchase_token.app_apple_id.clone();
160
161 if let Some(external_purchase_id) = &external_purchase_token.external_purchase_id {
162 if external_purchase_id.starts_with("SANDBOX") {
163 environment = Some(Environment::Sandbox)
164 } else {
165 environment = Some(Environment::Production)
166 }
167 } else {
168 environment = Some(Environment::Production)
169 }
170 } else {
171 bundle_id = None;
172 app_apple_id = None;
173 environment = None;
174 }
175
176 self.verify_notification_app_identifier_and_environment(bundle_id, app_apple_id, environment)?;
177
178 Ok(decoded_signed_notification)
179 }
180
181 fn verify_notification_app_identifier_and_environment(
182 &self,
183 bundle_id: Option<String>,
184 app_apple_id: Option<i64>,
185 environment: Option<Environment>,
186 ) -> Result<(), SignedDataVerifierError> {
187 if let Some(bundle_id) = bundle_id {
188 if bundle_id != self.bundle_id {
189 return Err(SignedDataVerifierError::InvalidAppIdentifier);
190 }
191 }
192
193 if self.environment == Environment::Production && self.app_apple_id != app_apple_id {
194 return Err(SignedDataVerifierError::InvalidAppIdentifier);
195 }
196
197 if let Some(environment) = environment {
198 if self.environment != Environment::LocalTesting && self.environment != environment {
199 return Err(SignedDataVerifierError::InvalidEnvironment);
200 }
201 }
202
203 Ok(())
204 }
205
206 pub fn verify_and_decode_app_transaction(
221 &self,
222 signed_app_transaction: &str,
223 ) -> Result<AppTransaction, SignedDataVerifierError> {
224 let decoded_app_transaction: AppTransaction = self.decode_signed_object(signed_app_transaction)?;
225
226 if decoded_app_transaction.bundle_id.as_ref() != Some(&self.bundle_id) {
227 return Err(SignedDataVerifierError::InvalidAppIdentifier);
228 }
229
230 if decoded_app_transaction.receipt_type.as_ref() != Some(&self.environment) {
231 return Err(SignedDataVerifierError::InvalidEnvironment);
232 }
233
234 Ok(decoded_app_transaction)
235 }
236
237 fn decode_signed_object<T: DeserializeOwned>(&self, signed_obj: &str) -> Result<T, SignedDataVerifierError> {
239 if self.environment == Environment::Xcode || self.environment == Environment::LocalTesting {
242 const EXPECTED_JWT_SEGMENTS: usize = 3;
243
244 let body_segments: Vec<&str> = signed_obj.split('.').collect();
245
246 if body_segments.len() != EXPECTED_JWT_SEGMENTS {
247 return Err(SignedDataVerifierError::VerificationFailure);
248 }
249
250 let _ = jsonwebtoken::decode_header(&signed_obj)?;
251 let body_data = base64_url_to_base64(body_segments[1]);
252
253 let decoded_body = match STANDARD.decode(body_data) {
254 Ok(decoded_body) => match serde_json::from_slice(&decoded_body) {
255 Ok(decoded) => decoded,
256 Err(_) => return Err(SignedDataVerifierError::VerificationFailure),
257 },
258 Err(_) => return Err(SignedDataVerifierError::VerificationFailure),
259 };
260
261 return Ok(decoded_body);
262 }
263
264 let header = jsonwebtoken::decode_header(signed_obj)?;
265
266 let Some(x5c) = header.x5c else {
267 return Err(SignedDataVerifierError::VerificationFailure);
268 };
269
270 if x5c.is_empty() {
271 return Err(SignedDataVerifierError::VerificationFailure);
272 }
273
274 let x5c: Result<Vec<Vec<u8>>, DecodeError> = x5c.iter().map(|c| c.as_der_bytes()).collect();
275 let chain = x5c?;
276
277 if header.alg != Algorithm::ES256 {
278 return Err(SignedDataVerifierError::VerificationFailure);
279 }
280
281 let pub_key = verify_chain(&chain, &self.root_certificates, None)?;
282 let pub_key = &pub_key[pub_key.len() - 65..];
283
284 let decoding_key = DecodingKey::from_ec_der(pub_key);
285 let claims: [&str; 0] = [];
286
287 let mut validator = Validation::new(Algorithm::ES256);
288 validator.validate_exp = false;
289 validator.set_required_spec_claims(&claims);
290
291 let payload = jsonwebtoken::decode::<T>(signed_obj, &decoding_key, &validator).expect("Expect Payload");
292 return Ok(payload.claims);
293 }
294}