mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(core): Add mTLS certificates for each request (#5636)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -197,12 +197,15 @@ impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, t | ||||
|         req: &types::RefreshTokenRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; | ||||
|         let req = Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Post) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) | ||||
|                 .url(&types::RefreshTokenType::get_url(self, req, connectors)?) | ||||
|                 .add_certificate(auth_details.certificate) | ||||
|                 .add_certificate_key(auth_details.certificate_key) | ||||
|                 .set_body(types::RefreshTokenType::get_request_body( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
| @ -326,6 +329,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P | ||||
|         req: &types::PaymentsAuthorizeRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; | ||||
|         Ok(Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Post) | ||||
| @ -336,6 +340,8 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P | ||||
|                 .headers(types::PaymentsAuthorizeType::get_headers( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
|                 .add_certificate(auth_details.certificate) | ||||
|                 .add_certificate_key(auth_details.certificate_key) | ||||
|                 .set_body(types::PaymentsAuthorizeType::get_request_body( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
| @ -406,12 +412,15 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe | ||||
|         req: &types::PaymentsSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; | ||||
|         Ok(Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Get) | ||||
|                 .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) | ||||
|                 .add_certificate(auth_details.certificate) | ||||
|                 .add_certificate_key(auth_details.certificate_key) | ||||
|                 .build(), | ||||
|         )) | ||||
|     } | ||||
| @ -480,6 +489,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme | ||||
|         req: &types::PaymentsCaptureRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; | ||||
|         Ok(Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Post) | ||||
| @ -488,6 +498,8 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme | ||||
|                 .headers(types::PaymentsCaptureType::get_headers( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
|                 .add_certificate(auth_details.certificate) | ||||
|                 .add_certificate_key(auth_details.certificate_key) | ||||
|                 .set_body(types::PaymentsCaptureType::get_request_body( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
| @ -597,6 +609,7 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon | ||||
|         req: &types::RefundsRouterData<api::Execute>, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; | ||||
|         let request = services::RequestBuilder::new() | ||||
|             .method(services::Method::Put) | ||||
|             .url(&types::RefundExecuteType::get_url(self, req, connectors)?) | ||||
| @ -604,6 +617,8 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon | ||||
|             .headers(types::RefundExecuteType::get_headers( | ||||
|                 self, req, connectors, | ||||
|             )?) | ||||
|             .add_certificate(auth_details.certificate) | ||||
|             .add_certificate_key(auth_details.certificate_key) | ||||
|             .set_body(types::RefundExecuteType::get_request_body( | ||||
|                 self, req, connectors, | ||||
|             )?) | ||||
| @ -679,12 +694,15 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse | ||||
|         req: &types::RefundSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; | ||||
|         Ok(Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Get) | ||||
|                 .url(&types::RefundSyncType::get_url(self, req, connectors)?) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::RefundSyncType::get_headers(self, req, connectors)?) | ||||
|                 .add_certificate(auth_details.certificate) | ||||
|                 .add_certificate_key(auth_details.certificate_key) | ||||
|                 .set_body(types::RefundSyncType::get_request_body( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
|  | ||||
| @ -133,15 +133,30 @@ impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for Itaub | ||||
| pub struct ItaubankAuthType { | ||||
|     pub(super) client_id: Secret<String>, | ||||
|     pub(super) client_secret: Secret<String>, | ||||
|     pub(super) certificate: Option<Secret<String>>, | ||||
|     pub(super) certificate_key: Option<Secret<String>>, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::ConnectorAuthType> for ItaubankAuthType { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { | ||||
|         match auth_type { | ||||
|             types::ConnectorAuthType::MultiAuthKey { | ||||
|                 api_key, | ||||
|                 key1, | ||||
|                 api_secret, | ||||
|                 key2, | ||||
|             } => Ok(Self { | ||||
|                 client_secret: api_key.to_owned(), | ||||
|                 client_id: key1.to_owned(), | ||||
|                 certificate: Some(api_secret.to_owned()), | ||||
|                 certificate_key: Some(key2.to_owned()), | ||||
|             }), | ||||
|             types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { | ||||
|                 client_secret: api_key.to_owned(), | ||||
|                 client_id: key1.to_owned(), | ||||
|                 certificate: None, | ||||
|                 certificate_key: None, | ||||
|             }), | ||||
|             _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), | ||||
|         } | ||||
|  | ||||
| @ -4,7 +4,6 @@ use api_models::{ | ||||
|     admin::{self as admin_types}, | ||||
|     enums as api_enums, routing as routing_types, | ||||
| }; | ||||
| use base64::Engine; | ||||
| use common_utils::{ | ||||
|     date_time, | ||||
|     ext_traits::{AsyncExt, Encode, OptionExt, ValueExt}, | ||||
| @ -24,7 +23,7 @@ use uuid::Uuid; | ||||
| #[cfg(any(feature = "v1", feature = "v2"))] | ||||
| use crate::types::transformers::ForeignFrom; | ||||
| use crate::{ | ||||
|     consts::{self, BASE64_ENGINE}, | ||||
|     consts, | ||||
|     core::{ | ||||
|         encryption::transfer_encryption_key, | ||||
|         errors::{self, RouterResponse, RouterResult, StorageErrorExt}, | ||||
| @ -35,7 +34,11 @@ use crate::{ | ||||
|     }, | ||||
|     db::StorageInterface, | ||||
|     routes::{metrics, SessionState}, | ||||
|     services::{self, api as service_api, authentication, pm_auth as payment_initiation_service}, | ||||
|     services::{ | ||||
|         self, | ||||
|         api::{self as service_api, client}, | ||||
|         authentication, pm_auth as payment_initiation_service, | ||||
|     }, | ||||
|     types::{ | ||||
|         self, | ||||
|         api::{self, admin}, | ||||
| @ -198,6 +201,10 @@ pub async fn create_merchant_account( | ||||
|     let identifier = km_types::Identifier::Merchant(merchant_id.clone()); | ||||
|     #[cfg(feature = "keymanager_create")] | ||||
|     { | ||||
|         use base64::Engine; | ||||
|  | ||||
|         use crate::consts::BASE64_ENGINE; | ||||
|  | ||||
|         keymanager::transfer_key_to_key_manager( | ||||
|             key_manager_state, | ||||
|             EncryptionTransferRequest { | ||||
| @ -1604,7 +1611,7 @@ impl<'a> ConnectorAuthTypeValidation<'a> { | ||||
|                 certificate, | ||||
|                 private_key, | ||||
|             } => { | ||||
|                 helpers::create_identity_from_certificate_and_key( | ||||
|                 client::create_identity_from_certificate_and_key( | ||||
|                     certificate.to_owned(), | ||||
|                     private_key.to_owned(), | ||||
|                 ) | ||||
| @ -1695,34 +1702,6 @@ impl<'a> PaymentMethodsEnabled<'a> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct CertificateAndCertificateKey<'a> { | ||||
|     certificate: &'a Secret<String>, | ||||
|     certificate_key: &'a Secret<String>, | ||||
| } | ||||
|  | ||||
| impl<'a> CertificateAndCertificateKey<'a> { | ||||
|     pub fn create_identity_from_certificate_and_key( | ||||
|         &self, | ||||
|     ) -> Result<reqwest::Identity, error_stack::Report<errors::ApiClientError>> { | ||||
|         let decoded_certificate = BASE64_ENGINE | ||||
|             .decode(self.certificate.clone().expose()) | ||||
|             .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|         let decoded_certificate_key = BASE64_ENGINE | ||||
|             .decode(self.certificate_key.clone().expose()) | ||||
|             .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|         let certificate = String::from_utf8(decoded_certificate) | ||||
|             .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|         let certificate_key = String::from_utf8(decoded_certificate_key) | ||||
|             .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|         reqwest::Identity::from_pkcs8_pem(certificate.as_bytes(), certificate_key.as_bytes()) | ||||
|             .change_context(errors::ApiClientError::CertificateDecodeFailed) | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ConnectorMetadata<'a> { | ||||
|     connector_metadata: &'a Option<pii::SecretSerdeValue>, | ||||
| } | ||||
| @ -1739,11 +1718,7 @@ impl<'a> ConnectorMetadata<'a> { | ||||
|             })? | ||||
|             .and_then(|metadata| metadata.get_apple_pay_certificates()) | ||||
|             .map(|(certificate, certificate_key)| { | ||||
|                 let certificate_and_certificate_key = CertificateAndCertificateKey { | ||||
|                     certificate: &certificate, | ||||
|                     certificate_key: &certificate_key, | ||||
|                 }; | ||||
|                 certificate_and_certificate_key.create_identity_from_certificate_and_key() | ||||
|                 client::create_identity_from_certificate_and_key(certificate, certificate_key) | ||||
|             }) | ||||
|             .transpose() | ||||
|             .change_context(errors::ApiErrorResponse::InvalidDataValue { | ||||
|  | ||||
| @ -99,41 +99,6 @@ use crate::{ | ||||
|     core::payment_methods::cards::create_encrypted_data, types::storage::CustomerUpdate::Update, | ||||
| }; | ||||
|  | ||||
| pub fn create_identity_from_certificate_and_key( | ||||
|     encoded_certificate: masking::Secret<String>, | ||||
|     encoded_certificate_key: masking::Secret<String>, | ||||
| ) -> Result<reqwest::Identity, error_stack::Report<errors::ApiClientError>> { | ||||
|     let decoded_certificate = BASE64_ENGINE | ||||
|         .decode(encoded_certificate.expose()) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let decoded_certificate_key = BASE64_ENGINE | ||||
|         .decode(encoded_certificate_key.expose()) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let certificate = String::from_utf8(decoded_certificate) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let certificate_key = String::from_utf8(decoded_certificate_key) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     reqwest::Identity::from_pkcs8_pem(certificate.as_bytes(), certificate_key.as_bytes()) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed) | ||||
| } | ||||
|  | ||||
| pub fn create_certificate( | ||||
|     encoded_certificate: masking::Secret<String>, | ||||
| ) -> Result<Vec<reqwest::Certificate>, error_stack::Report<errors::ApiClientError>> { | ||||
|     let decoded_certificate = BASE64_ENGINE | ||||
|         .decode(encoded_certificate.expose()) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let certificate = String::from_utf8(decoded_certificate) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed)?; | ||||
|     reqwest::Certificate::from_pem_bundle(certificate.as_bytes()) | ||||
|         .change_context(errors::ApiClientError::CertificateDecodeFailed) | ||||
| } | ||||
|  | ||||
| pub fn filter_mca_based_on_profile_and_connector_type( | ||||
|     merchant_connector_accounts: Vec<domain::MerchantConnectorAccount>, | ||||
|     profile_id: &id_type::ProfileId, | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| use std::time::Duration; | ||||
|  | ||||
| use base64::Engine; | ||||
| use error_stack::ResultExt; | ||||
| use http::{HeaderValue, Method}; | ||||
| use masking::PeekInterface; | ||||
| use masking::{ExposeInterface, PeekInterface}; | ||||
| use once_cell::sync::OnceCell; | ||||
| use reqwest::multipart::Form; | ||||
| use router_env::tracing_actix_web::RequestId; | ||||
| @ -10,11 +11,8 @@ use router_env::tracing_actix_web::RequestId; | ||||
| use super::{request::Maskable, Request}; | ||||
| use crate::{ | ||||
|     configs::settings::{Locker, Proxy}, | ||||
|     consts::LOCKER_HEALTH_CALL_PATH, | ||||
|     core::{ | ||||
|         errors::{ApiClientError, CustomResult}, | ||||
|         payments, | ||||
|     }, | ||||
|     consts::{BASE64_ENGINE, LOCKER_HEALTH_CALL_PATH}, | ||||
|     core::errors::{ApiClientError, CustomResult}, | ||||
|     routes::{app::settings::KeyManagerConfig, SessionState}, | ||||
| }; | ||||
|  | ||||
| @ -90,11 +88,11 @@ pub fn create_client( | ||||
|         (Some(encoded_certificate), Some(encoded_certificate_key)) => { | ||||
|             let client_builder = get_client_builder(proxy_config, should_bypass_proxy)?; | ||||
|  | ||||
|             let identity = payments::helpers::create_identity_from_certificate_and_key( | ||||
|             let identity = create_identity_from_certificate_and_key( | ||||
|                 encoded_certificate.clone(), | ||||
|                 encoded_certificate_key, | ||||
|             )?; | ||||
|             let certificate_list = payments::helpers::create_certificate(encoded_certificate)?; | ||||
|             let certificate_list = create_certificate(encoded_certificate)?; | ||||
|             let client_builder = certificate_list | ||||
|                 .into_iter() | ||||
|                 .fold(client_builder, |client_builder, certificate| { | ||||
| @ -102,6 +100,7 @@ pub fn create_client( | ||||
|                 }); | ||||
|             client_builder | ||||
|                 .identity(identity) | ||||
|                 .use_rustls_tls() | ||||
|                 .build() | ||||
|                 .change_context(ApiClientError::ClientConstructionFailed) | ||||
|                 .attach_printable("Failed to construct client with certificate and certificate key") | ||||
| @ -110,6 +109,42 @@ pub fn create_client( | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn create_identity_from_certificate_and_key( | ||||
|     encoded_certificate: masking::Secret<String>, | ||||
|     encoded_certificate_key: masking::Secret<String>, | ||||
| ) -> Result<reqwest::Identity, error_stack::Report<ApiClientError>> { | ||||
|     let decoded_certificate = BASE64_ENGINE | ||||
|         .decode(encoded_certificate.expose()) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let decoded_certificate_key = BASE64_ENGINE | ||||
|         .decode(encoded_certificate_key.expose()) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let certificate = String::from_utf8(decoded_certificate) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let certificate_key = String::from_utf8(decoded_certificate_key) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let key_chain = format!("{}{}", certificate_key, certificate); | ||||
|     reqwest::Identity::from_pem(key_chain.as_bytes()) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed) | ||||
| } | ||||
|  | ||||
| pub fn create_certificate( | ||||
|     encoded_certificate: masking::Secret<String>, | ||||
| ) -> Result<Vec<reqwest::Certificate>, error_stack::Report<ApiClientError>> { | ||||
|     let decoded_certificate = BASE64_ENGINE | ||||
|         .decode(encoded_certificate.expose()) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed)?; | ||||
|  | ||||
|     let certificate = String::from_utf8(decoded_certificate) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed)?; | ||||
|     reqwest::Certificate::from_pem_bundle(certificate.as_bytes()) | ||||
|         .change_context(ApiClientError::CertificateDecodeFailed) | ||||
| } | ||||
|  | ||||
| pub fn proxy_bypass_urls( | ||||
|     key_manager: &KeyManagerConfig, | ||||
|     locker: &Locker, | ||||
| @ -245,10 +280,8 @@ impl ProxyClient { | ||||
|             (Some(certificate), Some(certificate_key)) => { | ||||
|                 let client_builder = | ||||
|                     reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()); | ||||
|                 let identity = payments::helpers::create_identity_from_certificate_and_key( | ||||
|                     certificate, | ||||
|                     certificate_key, | ||||
|                 )?; | ||||
|                 let identity = | ||||
|                     create_identity_from_certificate_and_key(certificate, certificate_key)?; | ||||
|                 Ok(client_builder | ||||
|                     .identity(identity) | ||||
|                     .build() | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Sakil Mostak
					Sakil Mostak