Skip to main content

lockbook_server_lib/billing/
app_store_service.rs

1use crate::billing::app_store_model::{
2    EncodedNotificationResponseBody, NotificationResponseBody, TransactionInfo,
3};
4use crate::billing::billing_service::AppStoreNotificationError;
5use crate::config::AppleConfig;
6use crate::document_service::DocumentService;
7use crate::{ClientError, ServerError, ServerState};
8use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header};
9use lb_rs::model::api::{AppStoreAccountState, UnixTimeMillis, UpgradeAccountAppStoreError};
10use libsecp256k1::PublicKey;
11use serde::Serialize;
12use serde::de::DeserializeOwned;
13use tracing::debug;
14use warp::hyper::body::Bytes;
15use x509_parser::error::X509Error;
16use x509_parser::parse_x509_certificate;
17use x509_parser::prelude::X509Certificate;
18
19use super::app_store_client::AppStoreClient;
20use super::google_play_client::GooglePlayClient;
21use super::stripe_client::StripeClient;
22
23const SUBSCRIBED: u16 = 1;
24const EXPIRED: u16 = 2;
25const BILLING_RETRY: u16 = 3;
26const GRACE_PERIOD: u16 = 4;
27
28impl<S, A, G, D> ServerState<S, A, G, D>
29where
30    S: StripeClient,
31    A: AppStoreClient,
32    G: GooglePlayClient,
33    D: DocumentService,
34{
35    pub async fn get_public_key_from_tx(
36        &self, trans: &TransactionInfo,
37    ) -> Result<PublicKey, ServerError<AppStoreNotificationError>> {
38        let public_key: PublicKey = self
39            .index_db
40            .lock()
41            .await
42            .app_store_ids
43            .get()
44            .get(&trans.app_account_token)
45            .ok_or_else(|| {
46                internal!("There is no public_key related to this app_account_token: {:?}", trans)
47            })?
48            .0;
49
50        Ok(public_key)
51    }
52
53    pub async fn verify_details(
54        client: &A, config: &AppleConfig, app_account_token: &str, original_transaction_id: &str,
55    ) -> Result<(UnixTimeMillis, AppStoreAccountState), ServerError<UpgradeAccountAppStoreError>>
56    {
57        let (transaction, transaction_info) = client
58            .get_sub_status(config, original_transaction_id)
59            .await?;
60
61        debug!(?transaction_info.app_account_token, ?app_account_token, "Comparing verified app account token and with unverified");
62        debug!(?transaction.original_transaction_id, ?original_transaction_id, "Comparing verified original transaction id and with unverified");
63        debug!(?transaction.status, "Checking the subscription status.");
64
65        let account_state = match transaction.status {
66            SUBSCRIBED => AppStoreAccountState::Ok,
67            EXPIRED => AppStoreAccountState::Expired,
68            BILLING_RETRY => AppStoreAccountState::FailedToRenew,
69            GRACE_PERIOD => AppStoreAccountState::GracePeriod,
70            _ => return Err(internal!("Unknown subscription status.")),
71        };
72
73        if transaction_info.app_account_token != app_account_token
74            || transaction.original_transaction_id != original_transaction_id
75        {
76            return Err(ClientError(UpgradeAccountAppStoreError::InvalidAuthDetails));
77        }
78
79        Ok((transaction_info.expires_date as UnixTimeMillis, account_state))
80    }
81
82    pub fn decode_verify_notification(
83        config: &AppleConfig, request_body: &Bytes,
84    ) -> Result<NotificationResponseBody, ServerError<AppStoreNotificationError>> {
85        let encoded_resp: EncodedNotificationResponseBody = serde_json::from_slice(request_body)
86            .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
87
88        Self::validate_jwt(config, &encoded_resp.signed_payload)
89    }
90
91    fn validate_jwt<T: Serialize + DeserializeOwned>(
92        config: &AppleConfig, token: &str,
93    ) -> Result<T, ServerError<AppStoreNotificationError>> {
94        let header = decode_header(token)?;
95        let cert_chain: Vec<Vec<u8>> = header
96            .x5c
97            .ok_or(ClientError(AppStoreNotificationError::InvalidJWS))?
98            .into_iter()
99            .map(|cert| base64::decode(cert.as_bytes()))
100            .collect::<Result<Vec<Vec<u8>>, base64::DecodeError>>()?;
101
102        let certs: Vec<X509Certificate> = cert_chain
103            .iter()
104            .map(|cert| parse_x509_certificate(cert.as_slice()).map(|(_, cert)| cert))
105            .collect::<Result<Vec<X509Certificate>, x509_parser::nom::Err<X509Error>>>()
106            .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
107
108        for i in 0..certs.len() {
109            if i != certs.len() - 1 {
110                certs[i]
111                    .verify_signature(Some(&certs[i + 1].subject_pki))
112                    .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
113            } else {
114                certs[i]
115                    .verify_signature(Some(
116                        &parse_x509_certificate(config.apple_root_cert.as_slice())
117                            .map_err(|err| internal!("{:?}", err))?
118                            .1
119                            .subject_pki,
120                    ))
121                    .map_err(|_| ClientError(AppStoreNotificationError::InvalidJWS))?;
122            }
123        }
124
125        let pem = format!(
126            "-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----",
127            base64::encode(
128                certs
129                    .first()
130                    .ok_or(ClientError(AppStoreNotificationError::InvalidJWS))?
131                    .public_key()
132                    .raw
133            )
134        );
135        let key = DecodingKey::from_ec_pem(pem.as_bytes())?;
136
137        let mut validate = Validation::new(Algorithm::ES256);
138        validate.required_spec_claims.remove("exp");
139
140        Ok(decode::<T>(token, &key, &validate)?.claims)
141    }
142
143    pub fn decode_verify_transaction(
144        config: &AppleConfig, encoded_transaction: &str,
145    ) -> Result<TransactionInfo, ServerError<AppStoreNotificationError>> {
146        Self::validate_jwt(config, encoded_transaction)
147    }
148}