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:
Shankar Singh C
2023-09-07 22:49:10 +05:30
committed by GitHub
parent 81c6480bdf
commit 75ee632782
34 changed files with 1174 additions and 227 deletions

View File

@ -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)
}
}