mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(crypto): add RSA-SHA-256 signature verification (#9080)
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1904,6 +1904,7 @@ dependencies = [
|
|||||||
"reqwest 0.11.27",
|
"reqwest 0.11.27",
|
||||||
"ring 0.17.14",
|
"ring 0.17.14",
|
||||||
"router_env",
|
"router_env",
|
||||||
|
"rsa",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
@ -6973,6 +6974,7 @@ dependencies = [
|
|||||||
"pkcs1",
|
"pkcs1",
|
||||||
"pkcs8 0.10.2",
|
"pkcs8 0.10.2",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
|
"sha2",
|
||||||
"signature 2.2.0",
|
"signature 2.2.0",
|
||||||
"spki 0.7.3",
|
"spki 0.7.3",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
|||||||
@ -47,6 +47,7 @@ rand = "0.8.5"
|
|||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
reqwest = { version = "0.11.27", features = ["json", "native-tls", "gzip", "multipart"] }
|
reqwest = { version = "0.11.27", features = ["json", "native-tls", "gzip", "multipart"] }
|
||||||
ring = { version = "0.17.14", features = ["std", "wasm32_unknown_unknown_js"] }
|
ring = { version = "0.17.14", features = ["std", "wasm32_unknown_unknown_js"] }
|
||||||
|
rsa = {version = "0.9.8", features = ["sha2"]}
|
||||||
rust_decimal = "1.37"
|
rust_decimal = "1.37"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
semver = { version = "1.0.26", features = ["serde"] }
|
semver = { version = "1.0.26", features = ["serde"] }
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
//! Utilities for cryptographic algorithms
|
//! Utilities for cryptographic algorithms
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use base64::Engine;
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use masking::{ExposeInterface, Secret};
|
use masking::{ExposeInterface, Secret};
|
||||||
use md5;
|
use md5;
|
||||||
@ -12,8 +13,10 @@ use ring::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "logs")]
|
#[cfg(feature = "logs")]
|
||||||
use router_env::logger;
|
use router_env::logger;
|
||||||
|
use rsa::{pkcs8::DecodePublicKey, signature::Verifier};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
consts::BASE64_ENGINE,
|
||||||
errors::{self, CustomResult},
|
errors::{self, CustomResult},
|
||||||
pii::{self, EncryptionStrategy},
|
pii::{self, EncryptionStrategy},
|
||||||
};
|
};
|
||||||
@ -501,6 +504,52 @@ impl VerifySignature for Sha256 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Secure Hash Algorithm 256 with RSA public-key cryptosystem
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RsaSha256;
|
||||||
|
|
||||||
|
impl VerifySignature for RsaSha256 {
|
||||||
|
fn verify_signature(
|
||||||
|
&self,
|
||||||
|
secret: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
msg: &[u8],
|
||||||
|
) -> CustomResult<bool, errors::CryptoError> {
|
||||||
|
// create verifying key
|
||||||
|
let decoded_public_key = BASE64_ENGINE
|
||||||
|
.decode(secret)
|
||||||
|
.change_context(errors::CryptoError::SignatureVerificationFailed)
|
||||||
|
.attach_printable("base64 decoding failed")?;
|
||||||
|
|
||||||
|
let string_public_key = String::from_utf8(decoded_public_key)
|
||||||
|
.change_context(errors::CryptoError::SignatureVerificationFailed)
|
||||||
|
.attach_printable("utf8 to string parsing failed")?;
|
||||||
|
|
||||||
|
let rsa_public_key = rsa::RsaPublicKey::from_public_key_pem(&string_public_key)
|
||||||
|
.change_context(errors::CryptoError::SignatureVerificationFailed)
|
||||||
|
.attach_printable("rsa public key transformation failed")?;
|
||||||
|
|
||||||
|
let verifying_key = rsa::pkcs1v15::VerifyingKey::<rsa::sha2::Sha256>::new(rsa_public_key);
|
||||||
|
|
||||||
|
// transfrom the signature
|
||||||
|
let decoded_signature = BASE64_ENGINE
|
||||||
|
.decode(signature)
|
||||||
|
.change_context(errors::CryptoError::SignatureVerificationFailed)
|
||||||
|
.attach_printable("base64 decoding failed")?;
|
||||||
|
|
||||||
|
let rsa_signature = rsa::pkcs1v15::Signature::try_from(&decoded_signature[..])
|
||||||
|
.change_context(errors::CryptoError::SignatureVerificationFailed)
|
||||||
|
.attach_printable("rsa signature transformation failed")?;
|
||||||
|
|
||||||
|
// signature verification
|
||||||
|
verifying_key
|
||||||
|
.verify(msg, &rsa_signature)
|
||||||
|
.map(|_| true)
|
||||||
|
.change_context(errors::CryptoError::SignatureVerificationFailed)
|
||||||
|
.attach_printable("signature verification step failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// TripleDesEde3 hash function
|
/// TripleDesEde3 hash function
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg(feature = "crypto_openssl")]
|
#[cfg(feature = "crypto_openssl")]
|
||||||
@ -951,4 +1000,43 @@ mod crypto_tests {
|
|||||||
"Wrong signature should not verify"
|
"Wrong signature should not verify"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rsasha256_verify_signature() {
|
||||||
|
use base64::Engine;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rsa::{
|
||||||
|
pkcs8::EncodePublicKey,
|
||||||
|
signature::{RandomizedSigner, SignatureEncoding},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::consts::BASE64_ENGINE;
|
||||||
|
|
||||||
|
let mut rng = OsRng;
|
||||||
|
|
||||||
|
let bits = 2048;
|
||||||
|
let private_key = rsa::RsaPrivateKey::new(&mut rng, bits).expect("keygen failed");
|
||||||
|
let signing_key = rsa::pkcs1v15::SigningKey::<rsa::sha2::Sha256>::new(private_key.clone());
|
||||||
|
|
||||||
|
let message = "{ This is a test message :) }".as_bytes();
|
||||||
|
|
||||||
|
let signature = signing_key.sign_with_rng(&mut rng, message);
|
||||||
|
let encoded_signature = BASE64_ENGINE.encode(signature.to_bytes());
|
||||||
|
|
||||||
|
let rsa_public_key = private_key.to_public_key();
|
||||||
|
let pem_format_public_key = rsa_public_key
|
||||||
|
.to_public_key_pem(rsa::pkcs8::LineEnding::LF)
|
||||||
|
.expect("transformation failed");
|
||||||
|
let encoded_pub_key = BASE64_ENGINE.encode(&pem_format_public_key[..]);
|
||||||
|
|
||||||
|
let right_verified = super::RsaSha256
|
||||||
|
.verify_signature(
|
||||||
|
encoded_pub_key.as_bytes(),
|
||||||
|
encoded_signature.as_bytes(),
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
.expect("Right signature verification result");
|
||||||
|
|
||||||
|
assert!(right_verified);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -758,6 +758,50 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Wise {}
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl IncomingWebhook for Wise {
|
impl IncomingWebhook for Wise {
|
||||||
|
fn get_webhook_source_verification_algorithm(
|
||||||
|
&self,
|
||||||
|
_request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
) -> CustomResult<Box<dyn common_utils::crypto::VerifySignature + Send>, ConnectorError> {
|
||||||
|
Ok(Box::new(common_utils::crypto::RsaSha256))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_webhook_source_verification_signature(
|
||||||
|
&self,
|
||||||
|
#[cfg(feature = "payouts")] request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
#[cfg(not(feature = "payouts"))] _request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
||||||
|
) -> CustomResult<Vec<u8>, ConnectorError> {
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
{
|
||||||
|
request
|
||||||
|
.headers
|
||||||
|
.get("X-Signature-SHA256")
|
||||||
|
.map(|header_value| header_value.as_bytes().to_vec())
|
||||||
|
.ok_or(ConnectorError::WebhookSignatureNotFound.into())
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "payouts"))]
|
||||||
|
{
|
||||||
|
Err(report!(ConnectorError::WebhooksNotImplemented))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_webhook_source_verification_message(
|
||||||
|
&self,
|
||||||
|
#[cfg(feature = "payouts")] request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
#[cfg(not(feature = "payouts"))] _request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
_merchant_id: &common_utils::id_type::MerchantId,
|
||||||
|
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
||||||
|
) -> CustomResult<Vec<u8>, ConnectorError> {
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
{
|
||||||
|
Ok(request.body.to_vec())
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "payouts"))]
|
||||||
|
{
|
||||||
|
Err(report!(ConnectorError::WebhooksNotImplemented))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_webhook_object_reference_id(
|
fn get_webhook_object_reference_id(
|
||||||
&self,
|
&self,
|
||||||
#[cfg(feature = "payouts")] request: &IncomingWebhookRequestDetails<'_>,
|
#[cfg(feature = "payouts")] request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
|||||||
Reference in New Issue
Block a user