mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(apple_pay): add support for pre decrypted apple pay token (#2056)
Co-authored-by: Sangamesh <sangamesh.kulkarni@juspay.in>
This commit is contained in:
		| @ -9,19 +9,31 @@ use data_models::payments::payment_intent::PaymentIntent; | ||||
| use diesel_models::{enums, payment_attempt::PaymentAttempt}; | ||||
| // TODO : Evaluate all the helper functions () | ||||
| use error_stack::{report, IntoReport, ResultExt}; | ||||
| #[cfg(feature = "kms")] | ||||
| use external_services::kms; | ||||
| use josekit::jwe; | ||||
| use masking::{ExposeInterface, PeekInterface}; | ||||
| #[cfg(feature = "kms")] | ||||
| use openssl::derive::Deriver; | ||||
| #[cfg(feature = "kms")] | ||||
| use openssl::pkey::PKey; | ||||
| #[cfg(feature = "kms")] | ||||
| use openssl::symm::{decrypt_aead, Cipher}; | ||||
| use router_env::{instrument, logger, tracing}; | ||||
| use time::Duration; | ||||
| use uuid::Uuid; | ||||
| #[cfg(feature = "kms")] | ||||
| use x509_parser::parse_x509_certificate; | ||||
|  | ||||
| use super::{ | ||||
|     operations::{BoxedOperation, Operation, PaymentResponse}, | ||||
|     CustomerDetails, PaymentData, | ||||
| }; | ||||
| #[cfg(feature = "kms")] | ||||
| use crate::connector; | ||||
| use crate::{ | ||||
|     configs::settings::{ConnectorRequestReferenceIdConfig, Server}, | ||||
|     consts, | ||||
|     consts::{self, BASE64_ENGINE}, | ||||
|     core::{ | ||||
|         errors::{self, CustomResult, RouterResult, StorageErrorExt}, | ||||
|         payment_methods::{cards, vault}, | ||||
| @ -51,12 +63,12 @@ pub fn create_identity_from_certificate_and_key( | ||||
|     encoded_certificate: String, | ||||
|     encoded_certificate_key: String, | ||||
| ) -> Result<reqwest::Identity, error_stack::Report<errors::ApiClientError>> { | ||||
|     let decoded_certificate = consts::BASE64_ENGINE | ||||
|     let decoded_certificate = BASE64_ENGINE | ||||
|         .decode(encoded_certificate) | ||||
|         .into_report() | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let decoded_certificate_key = consts::BASE64_ENGINE | ||||
|     let decoded_certificate_key = BASE64_ENGINE | ||||
|         .decode(encoded_certificate_key) | ||||
|         .into_report() | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
| @ -2383,6 +2395,7 @@ impl MerchantConnectorAccountType { | ||||
|             Self::CacheVal(val) => val.metadata.to_owned(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn get_connector_account_details(&self) -> serde_json::Value { | ||||
|         match self { | ||||
|             Self::DbVal(val) => val.connector_account_details.peek().to_owned(), | ||||
| @ -3006,3 +3019,180 @@ pub fn validate_customer_access( | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, serde::Deserialize)] | ||||
| pub struct ApplePayData { | ||||
|     version: masking::Secret<String>, | ||||
|     data: masking::Secret<String>, | ||||
|     signature: masking::Secret<String>, | ||||
|     header: ApplePayHeader, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct ApplePayHeader { | ||||
|     ephemeral_public_key: masking::Secret<String>, | ||||
|     public_key_hash: masking::Secret<String>, | ||||
|     transaction_id: masking::Secret<String>, | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "kms")] | ||||
| impl ApplePayData { | ||||
|     pub fn token_json( | ||||
|         wallet_data: api_models::payments::WalletData, | ||||
|     ) -> CustomResult<Self, errors::ConnectorError> { | ||||
|         let json_wallet_data: Self = | ||||
|             connector::utils::WalletData::get_wallet_token_as_json(&wallet_data)?; | ||||
|         Ok(json_wallet_data) | ||||
|     } | ||||
|  | ||||
|     pub async fn decrypt( | ||||
|         &self, | ||||
|         state: &AppState, | ||||
|     ) -> CustomResult<serde_json::Value, errors::ApplePayDecryptionError> { | ||||
|         let merchant_id = self.merchant_id(state).await?; | ||||
|         let shared_secret = self.shared_secret(state).await?; | ||||
|         let symmetric_key = self.symmetric_key(&merchant_id, &shared_secret)?; | ||||
|         let decrypted = self.decrypt_ciphertext(&symmetric_key)?; | ||||
|         let parsed_decrypted: serde_json::Value = serde_json::from_str(&decrypted) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; | ||||
|         Ok(parsed_decrypted) | ||||
|     } | ||||
|  | ||||
|     pub async fn merchant_id( | ||||
|         &self, | ||||
|         state: &AppState, | ||||
|     ) -> CustomResult<String, errors::ApplePayDecryptionError> { | ||||
|         let cert_data = kms::get_kms_client(&state.conf.kms) | ||||
|             .await | ||||
|             .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc) | ||||
|             .await | ||||
|             .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; | ||||
|  | ||||
|         let base64_decode_cert_data = BASE64_ENGINE | ||||
|             .decode(cert_data) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?; | ||||
|  | ||||
|         // Parsing the certificate using x509-parser | ||||
|         let (_, certificate) = parse_x509_certificate(&base64_decode_cert_data) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::CertificateParsingFailed) | ||||
|             .attach_printable("Error parsing apple pay PPC")?; | ||||
|  | ||||
|         // Finding the merchant ID extension | ||||
|         let apple_pay_m_id = certificate | ||||
|             .extensions() | ||||
|             .iter() | ||||
|             .find(|extension| { | ||||
|                 extension | ||||
|                     .oid | ||||
|                     .to_string() | ||||
|                     .eq(consts::MERCHANT_ID_FIELD_EXTENSION_ID) | ||||
|             }) | ||||
|             .map(|ext| { | ||||
|                 let merchant_id = String::from_utf8_lossy(ext.value) | ||||
|                     .trim() | ||||
|                     .trim_start_matches('@') | ||||
|                     .to_string(); | ||||
|  | ||||
|                 merchant_id | ||||
|             }) | ||||
|             .ok_or(errors::ApplePayDecryptionError::MissingMerchantId) | ||||
|             .into_report() | ||||
|             .attach_printable("Unable to find merchant ID extension in the certificate")?; | ||||
|  | ||||
|         Ok(apple_pay_m_id) | ||||
|     } | ||||
|  | ||||
|     pub async fn shared_secret( | ||||
|         &self, | ||||
|         state: &AppState, | ||||
|     ) -> CustomResult<Vec<u8>, errors::ApplePayDecryptionError> { | ||||
|         let public_ec_bytes = BASE64_ENGINE | ||||
|             .decode(self.header.ephemeral_public_key.peek().as_bytes()) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?; | ||||
|  | ||||
|         let public_key = PKey::public_key_from_der(&public_ec_bytes) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::KeyDeserializationFailed) | ||||
|             .attach_printable("Failed to deserialize the public key")?; | ||||
|  | ||||
|         let decrypted_apple_pay_ppc_key = kms::get_kms_client(&state.conf.kms) | ||||
|             .await | ||||
|             .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc_key) | ||||
|             .await | ||||
|             .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; | ||||
|  | ||||
|         // Create PKey objects from EcKey | ||||
|         let private_key = PKey::private_key_from_pem(decrypted_apple_pay_ppc_key.as_bytes()) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::KeyDeserializationFailed) | ||||
|             .attach_printable("Failed to deserialize the private key")?; | ||||
|  | ||||
|         // Create the Deriver object and set the peer public key | ||||
|         let mut deriver = Deriver::new(&private_key) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::DerivingSharedSecretKeyFailed) | ||||
|             .attach_printable("Failed to create a deriver for the private key")?; | ||||
|  | ||||
|         deriver | ||||
|             .set_peer(&public_key) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::DerivingSharedSecretKeyFailed) | ||||
|             .attach_printable("Failed to set the peer key for the secret derivation")?; | ||||
|  | ||||
|         // Compute the shared secret | ||||
|         let shared_secret = deriver | ||||
|             .derive_to_vec() | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::DerivingSharedSecretKeyFailed) | ||||
|             .attach_printable("Final key derivation failed")?; | ||||
|         Ok(shared_secret) | ||||
|     } | ||||
|  | ||||
|     pub fn symmetric_key( | ||||
|         &self, | ||||
|         merchant_id: &str, | ||||
|         shared_secret: &[u8], | ||||
|     ) -> CustomResult<Vec<u8>, errors::ApplePayDecryptionError> { | ||||
|         let kdf_algorithm = b"\x0did-aes256-GCM"; | ||||
|         let kdf_party_v = hex::decode(merchant_id) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?; | ||||
|         let kdf_party_u = b"Apple"; | ||||
|         let kdf_info = [&kdf_algorithm[..], kdf_party_u, &kdf_party_v[..]].concat(); | ||||
|  | ||||
|         let mut hash = openssl::sha::Sha256::new(); | ||||
|         hash.update(b"\x00\x00\x00"); | ||||
|         hash.update(b"\x01"); | ||||
|         hash.update(shared_secret); | ||||
|         hash.update(&kdf_info[..]); | ||||
|         let symmetric_key = hash.finish(); | ||||
|         Ok(symmetric_key.to_vec()) | ||||
|     } | ||||
|  | ||||
|     pub fn decrypt_ciphertext( | ||||
|         &self, | ||||
|         symmetric_key: &[u8], | ||||
|     ) -> CustomResult<String, errors::ApplePayDecryptionError> { | ||||
|         let data = BASE64_ENGINE | ||||
|             .decode(self.data.peek().as_bytes()) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?; | ||||
|         let iv = [0u8; 16]; //Initialization vector IV is typically used in AES-GCM (Galois/Counter Mode) encryption for randomizing the encryption process. | ||||
|         let ciphertext = &data[..data.len() - 16]; | ||||
|         let tag = &data[data.len() - 16..]; | ||||
|         let cipher = Cipher::aes_256_gcm(); | ||||
|         let decrypted_data = decrypt_aead(cipher, symmetric_key, Some(&iv), &[], ciphertext, tag) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; | ||||
|         let decrypted = String::from_utf8(decrypted_data) | ||||
|             .into_report() | ||||
|             .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; | ||||
|  | ||||
|         Ok(decrypted) | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Shankar Singh C
					Shankar Singh C