mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
refactor(router): make routes public and move crypto module to common utils (#176)
Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
353
crates/common_utils/src/crypto.rs
Normal file
353
crates/common_utils/src/crypto.rs
Normal file
@ -0,0 +1,353 @@
|
||||
//! Utilities for cryptographic algorithms
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use ring::{aead, hmac};
|
||||
|
||||
use crate::errors::{self, CustomResult};
|
||||
|
||||
const RING_ERR_UNSPECIFIED: &str = "ring::error::Unspecified";
|
||||
|
||||
/// Trait for cryptographically signing messages
|
||||
pub trait SignMessage {
|
||||
/// Takes in a secret and a message and returns the calculated signature as bytes
|
||||
fn sign_message(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
_msg: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::CryptoError>;
|
||||
}
|
||||
|
||||
/// Trait for cryptographically verifying a message against a signature
|
||||
pub trait VerifySignature {
|
||||
/// Takes in a secret, the signature and the message and verifies the message
|
||||
/// against the signature
|
||||
fn verify_signature(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
_signature: &[u8],
|
||||
_msg: &[u8],
|
||||
) -> CustomResult<bool, errors::CryptoError>;
|
||||
}
|
||||
|
||||
/// Trait for cryptographically encoding a message
|
||||
pub trait EncodeMessage {
|
||||
/// Takes in a secret and the message and encodes it, returning bytes
|
||||
fn encode_message(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
_msg: &[u8],
|
||||
) -> CustomResult<(Vec<u8>, Vec<u8>), errors::CryptoError>;
|
||||
}
|
||||
|
||||
/// Trait for cryptographically decoding a message
|
||||
pub trait DecodeMessage {
|
||||
/// Takes in a secret, an encoded messages and attempts to decode it, returning bytes
|
||||
fn decode_message(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
_msg: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::CryptoError>;
|
||||
}
|
||||
|
||||
/// Represents no cryptographic algorithm.
|
||||
/// Implements all crypto traits and acts like a Nop
|
||||
#[derive(Debug)]
|
||||
pub struct NoAlgorithm;
|
||||
|
||||
impl SignMessage for NoAlgorithm {
|
||||
fn sign_message(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
_msg: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::CryptoError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifySignature for NoAlgorithm {
|
||||
fn verify_signature(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
_signature: &[u8],
|
||||
_msg: &[u8],
|
||||
) -> CustomResult<bool, errors::CryptoError> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeMessage for NoAlgorithm {
|
||||
fn encode_message(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<(Vec<u8>, Vec<u8>), errors::CryptoError> {
|
||||
Ok((msg.to_vec(), Vec::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodeMessage for NoAlgorithm {
|
||||
fn decode_message(
|
||||
&self,
|
||||
_secret: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::CryptoError> {
|
||||
Ok(msg.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the HMAC-SHA-256 algorithm
|
||||
#[derive(Debug)]
|
||||
pub struct HmacSha256;
|
||||
|
||||
impl SignMessage for HmacSha256 {
|
||||
fn sign_message(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::CryptoError> {
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA256, secret);
|
||||
Ok(hmac::sign(&key, msg).as_ref().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifySignature for HmacSha256 {
|
||||
fn verify_signature(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
signature: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<bool, errors::CryptoError> {
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA256, secret);
|
||||
|
||||
Ok(hmac::verify(&key, msg, signature).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the HMAC-SHA-512 algorithm
|
||||
#[derive(Debug)]
|
||||
pub struct HmacSha512;
|
||||
|
||||
impl SignMessage for HmacSha512 {
|
||||
fn sign_message(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::CryptoError> {
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA512, secret);
|
||||
Ok(hmac::sign(&key, msg).as_ref().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifySignature for HmacSha512 {
|
||||
fn verify_signature(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
signature: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<bool, errors::CryptoError> {
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA512, secret);
|
||||
|
||||
Ok(hmac::verify(&key, msg, signature).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the GCM-AES-256 algorithm
|
||||
#[derive(Debug)]
|
||||
pub struct GcmAes256 {
|
||||
nonce: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EncodeMessage for GcmAes256 {
|
||||
fn encode_message(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<(Vec<u8>, Vec<u8>), errors::CryptoError> {
|
||||
let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, secret)
|
||||
.map_err(|_| errors::CryptoError::EncodingFailed)
|
||||
.into_report()
|
||||
.attach_printable(RING_ERR_UNSPECIFIED)?;
|
||||
|
||||
let nonce = aead::Nonce::try_assume_unique_for_key(&self.nonce)
|
||||
.map_err(|_| errors::CryptoError::EncodingFailed)
|
||||
.into_report()
|
||||
.attach_printable(RING_ERR_UNSPECIFIED)?;
|
||||
|
||||
let sealing_key = aead::LessSafeKey::new(unbound_key);
|
||||
let mut mutable_msg = msg.to_vec();
|
||||
|
||||
let tag = sealing_key
|
||||
.seal_in_place_separate_tag(nonce, aead::Aad::empty(), &mut mutable_msg)
|
||||
.map_err(|_| errors::CryptoError::EncodingFailed)
|
||||
.into_report()
|
||||
.attach_printable(RING_ERR_UNSPECIFIED)?;
|
||||
|
||||
Ok((mutable_msg, tag.as_ref().to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodeMessage for GcmAes256 {
|
||||
fn decode_message(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
msg: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::CryptoError> {
|
||||
let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, secret)
|
||||
.map_err(|_| errors::CryptoError::DecodingFailed)
|
||||
.into_report()
|
||||
.attach_printable(RING_ERR_UNSPECIFIED)?;
|
||||
|
||||
let nonce = aead::Nonce::try_assume_unique_for_key(&self.nonce)
|
||||
.map_err(|_| errors::CryptoError::DecodingFailed)
|
||||
.into_report()
|
||||
.attach_printable(RING_ERR_UNSPECIFIED)?;
|
||||
|
||||
let opening_key = aead::LessSafeKey::new(unbound_key);
|
||||
|
||||
let mut mutable_msg = msg.to_vec();
|
||||
|
||||
let output = opening_key
|
||||
.open_in_place(nonce, aead::Aad::empty(), &mut mutable_msg)
|
||||
.map_err(|_| errors::CryptoError::DecodingFailed)
|
||||
.into_report()
|
||||
.attach_printable(RING_ERR_UNSPECIFIED)?;
|
||||
|
||||
Ok(output.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod crypto_tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
use super::{DecodeMessage, EncodeMessage, SignMessage, VerifySignature};
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha256_sign_message() {
|
||||
let message = r#"{"type":"payment_intent"}"#.as_bytes();
|
||||
let secret = "hmac_secret_1234".as_bytes();
|
||||
let right_signature =
|
||||
hex::decode("d5550730377011948f12cc28889bee590d2a5434d6f54b87562f2dbc2657823e")
|
||||
.expect("Right signature decoding");
|
||||
|
||||
let signature = super::HmacSha256
|
||||
.sign_message(secret, message)
|
||||
.expect("Signature");
|
||||
|
||||
assert_eq!(signature, right_signature);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha256_verify_signature() {
|
||||
let right_signature =
|
||||
hex::decode("d5550730377011948f12cc28889bee590d2a5434d6f54b87562f2dbc2657823e")
|
||||
.expect("Right signature decoding");
|
||||
let wrong_signature =
|
||||
hex::decode("d5550730377011948f12cc28889bee590d2a5434d6f54b87562f2dbc2657823f")
|
||||
.expect("Wrong signature decoding");
|
||||
let secret = "hmac_secret_1234".as_bytes();
|
||||
let data = r#"{"type":"payment_intent"}"#.as_bytes();
|
||||
|
||||
let right_verified = super::HmacSha256
|
||||
.verify_signature(secret, &right_signature, data)
|
||||
.expect("Right signature verification result");
|
||||
|
||||
assert!(right_verified);
|
||||
|
||||
let wrong_verified = super::HmacSha256
|
||||
.verify_signature(secret, &wrong_signature, data)
|
||||
.expect("Wrong signature verification result");
|
||||
|
||||
assert!(!wrong_verified);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha512_sign_message() {
|
||||
let message = r#"{"type":"payment_intent"}"#.as_bytes();
|
||||
let secret = "hmac_secret_1234".as_bytes();
|
||||
let right_signature = hex::decode("38b0bc1ea66b14793e39cd58e93d37b799a507442d0dd8d37443fa95dec58e57da6db4742636fea31201c48e57a66e73a308a2e5a5c6bb831e4e39fe2227c00f")
|
||||
.expect("signature decoding");
|
||||
|
||||
let signature = super::HmacSha512
|
||||
.sign_message(secret, message)
|
||||
.expect("Signature");
|
||||
|
||||
assert_eq!(signature, right_signature);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha512_verify_signature() {
|
||||
let right_signature = hex::decode("38b0bc1ea66b14793e39cd58e93d37b799a507442d0dd8d37443fa95dec58e57da6db4742636fea31201c48e57a66e73a308a2e5a5c6bb831e4e39fe2227c00f")
|
||||
.expect("signature decoding");
|
||||
let wrong_signature =
|
||||
hex::decode("d5550730377011948f12cc28889bee590d2a5434d6f54b87562f2dbc2657823f")
|
||||
.expect("Wrong signature decoding");
|
||||
let secret = "hmac_secret_1234".as_bytes();
|
||||
let data = r#"{"type":"payment_intent"}"#.as_bytes();
|
||||
|
||||
let right_verified = super::HmacSha512
|
||||
.verify_signature(secret, &right_signature, data)
|
||||
.expect("Right signature verification result");
|
||||
|
||||
assert!(right_verified);
|
||||
|
||||
let wrong_verified = super::HmacSha256
|
||||
.verify_signature(secret, &wrong_signature, data)
|
||||
.expect("Wrong signature verification result");
|
||||
|
||||
assert!(!wrong_verified);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gcm_aes_256_encode_message() {
|
||||
let message = r#"{"type":"PAYMENT"}"#.as_bytes();
|
||||
let secret =
|
||||
hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f")
|
||||
.expect("Secret decoding");
|
||||
let nonce = hex::decode("000000000000000000000000").expect("Nonce hex decoding");
|
||||
let actual_encoded_message =
|
||||
hex::decode("0A3471C72D9BE49A8520F79C66BBD9A12FF9").expect("Message decoding");
|
||||
let actual_auth_tag =
|
||||
hex::decode("CE573FB7A41AB78E743180DC83FF09BD").expect("Auth tag decoding");
|
||||
|
||||
let algorithm = super::GcmAes256 {
|
||||
nonce: nonce.to_vec(),
|
||||
};
|
||||
|
||||
let (encoded_message, auth_tag) = algorithm
|
||||
.encode_message(&secret, message)
|
||||
.expect("Encoded message and tag");
|
||||
|
||||
assert_eq!(encoded_message, actual_encoded_message);
|
||||
assert_eq!(auth_tag, actual_auth_tag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gcm_aes_256_decode_message() {
|
||||
let right_secret =
|
||||
hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f")
|
||||
.expect("Secret decoding");
|
||||
let wrong_secret =
|
||||
hex::decode("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0e")
|
||||
.expect("Secret decoding");
|
||||
let nonce = hex::decode("000000000000000000000000").expect("Nonce hex decoding");
|
||||
let mut auth_tag =
|
||||
hex::decode("CE573FB7A41AB78E743180DC83FF09BD").expect("Auth tag decoding");
|
||||
let mut message =
|
||||
hex::decode("0A3471C72D9BE49A8520F79C66BBD9A12FF9").expect("Message decoding");
|
||||
|
||||
message.append(&mut auth_tag);
|
||||
|
||||
let algorithm = super::GcmAes256 {
|
||||
nonce: nonce.to_vec(),
|
||||
};
|
||||
|
||||
let decoded = algorithm
|
||||
.decode_message(&right_secret, &message)
|
||||
.expect("Decoded message");
|
||||
|
||||
assert_eq!(decoded, r#"{"type":"PAYMENT"}"#.as_bytes());
|
||||
|
||||
let err_decoded = algorithm.decode_message(&wrong_secret, &message);
|
||||
|
||||
assert!(err_decoded.is_err());
|
||||
}
|
||||
}
|
||||
@ -54,3 +54,20 @@ pub enum ValidationError {
|
||||
#[error("{message}")]
|
||||
InvalidValue { message: String },
|
||||
}
|
||||
|
||||
/// Cryptograpic algorithm errors
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CryptoError {
|
||||
/// The cryptographic algorithm was unable to encode the message
|
||||
#[error("Failed to encode given message")]
|
||||
EncodingFailed,
|
||||
/// The cryptographic algorithm was unable to decode the message
|
||||
#[error("Failed to decode given message")]
|
||||
DecodingFailed,
|
||||
/// The cryptographic algorithm was unable to sign the message
|
||||
#[error("Failed to sign message")]
|
||||
MessageSigningFailed,
|
||||
/// The cryptographic algorithm was unable to verify the given signature
|
||||
#[error("Failed to verify signature")]
|
||||
SignatureVerificationFailed,
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR" ), "/", "README.md"))]
|
||||
|
||||
pub mod consts;
|
||||
pub mod crypto;
|
||||
pub mod custom_serde;
|
||||
pub mod errors;
|
||||
pub mod ext_traits;
|
||||
|
||||
Reference in New Issue
Block a user