From 3992cff602a214edee8f0f0fd0c9d852db7960ca Mon Sep 17 00:00:00 2001 From: Nishant Joshi Date: Mon, 5 Feb 2024 18:57:47 +0530 Subject: [PATCH 01/10] feat(hmac): add implementation for hmac-sha512 --- Cargo.toml | 5 ++ benches/hashing.rs | 54 ++++++++++++++ src/crypto/sha.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 benches/hashing.rs diff --git a/Cargo.toml b/Cargo.toml index dede700..553217b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,3 +92,8 @@ harness = false [[bench]] name = "encryption" harness = false + + +[[bench]] +name = "hashing" +harness = false diff --git a/benches/hashing.rs b/benches/hashing.rs new file mode 100644 index 0000000..b5edc4a --- /dev/null +++ b/benches/hashing.rs @@ -0,0 +1,54 @@ +#![allow(clippy::expect_used)] +#![allow(clippy::missing_panics_doc)] + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use tartarus::crypto::{sha::HmacSha512, Encode}; + +const ITERATION: u32 = 14; + +criterion_main!(benches); +criterion_group!(benches, criterion_hmac_sha512); + +macro_rules! const_iter { + ($algo:ident, $body:block, $key:ident, $($ty:expr),*) => { + $( + let $algo = $ty($key.clone()); + $body + )* + }; +} + +pub fn criterion_hmac_sha512(c: &mut Criterion) { + let key: masking::Secret<_> = (0..1000) + .map(|_| rand::random::()) + .collect::>() + .into(); + + + const_iter!( + algo, + { + let mut group = c.benchmark_group(format!("{}", algo)); + (1..ITERATION).for_each(|po| { + let max: u64 = (2_u64).pow(po); + let value = (0..max).map(|_| rand::random::()).collect::>(); + let hashed = algo.encode(value.clone()).unwrap(); + group.throughput(criterion::Throughput::Bytes(max)); + group.bench_with_input( + criterion::BenchmarkId::from_parameter(format!("{}", max)), + &value, + |b, value| { + b.iter(|| { + black_box(algo.encode(black_box(value.clone())).unwrap() == hashed) + }) + }, + ); + }) + }, + key, + HmacSha512::<1>::new, + HmacSha512::<10>::new, + HmacSha512::<100>::new, + HmacSha512::<1000>::new + ); +} diff --git a/src/crypto/sha.rs b/src/crypto/sha.rs index 37f7e58..dad73f4 100644 --- a/src/crypto/sha.rs +++ b/src/crypto/sha.rs @@ -1,3 +1,6 @@ +use masking::PeekInterface; +use ring::hmac; + use crate::error; /// @@ -13,3 +16,174 @@ impl super::Encode, Vec> for Sha512 { Ok(digest.as_ref().to_vec()) } } + + +/// +/// Type providing encoding functional to perform HMAC-SHA512 hashing +/// +/// # Example +/// +///``` +/// use tartarus::crypto::sha::HmacSha512; +/// use tartarus::crypto::Encode; +/// +/// let data = "Hello, World!"; +/// let key = "key"; +/// let algo = HmacSha512::<1>::new(key.as_bytes().to_vec()); +/// let hash = algo.encode(data.as_bytes().to_vec()).unwrap(); +/// +/// ``` +/// +/// This will not compile if `N` is less than or equal to 0. +/// +/// ```compile_fail +/// +/// use tartarus::crypto::sha::HmacSha512; +/// use tartarus::crypto::Encode; +/// +/// let key = "key"; +/// let algo = HmacSha512::<0>::new(key.as_bytes().to_vec()); +/// +/// +/// ``` +/// +/// +pub struct HmacSha512(masking::Secret>); + +impl HmacSha512 { + pub fn new(key: masking::Secret>) -> Self { + _ = ::VALID; + + Self(key) + } +} + +impl std::fmt::Display for HmacSha512 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "HmacSha512<{}>", N) + } +} + + + +impl super::Encode, Vec> for HmacSha512 { + type ReturnType = Result>; + + fn encode(&self, input: Vec) -> Self::ReturnType> { + let key = hmac::Key::new(ring::hmac::HMAC_SHA512, &self.0.peek()); + let first = hmac::sign(&key, &input); + + let signature = (0..=(N - 1)).fold(first, |input, _| hmac::sign(&key, input.as_ref())); + + Ok(signature.as_ref().to_vec()) + } +} + +trait AssertGt0 { + const VALID: (); +} + +impl AssertGt0 for HmacSha512 { + const VALID: () = assert!(N > 0); +} + +#[cfg(test)] +mod tests { + //! + //! Testing HMAC-SHA512 encoding consists of 3 variables. + //! 1. The input data + //! 2. The Key + //! 3. The `N` value + //! + + use crate::crypto::Encode; + + use super::*; + + #[test] + fn test_input_data_equal() { + let data1 = "Hello, World!"; + let data2 = "Hello, World!"; + let key = "key"; + + let algo = HmacSha512::<1>::new(key.as_bytes().to_vec().into()); + + let hash1 = algo.encode(data1.as_bytes().to_vec()).unwrap(); + let hash2 = algo.encode(data2.as_bytes().to_vec()).unwrap(); + + assert_eq!(hash1, hash2); + } + + #[test] + fn test_input_data_not_equal() { + let data1 = "Hello, World!"; + let data2 = "Hello, world"; + let key = "key"; + + let algo = HmacSha512::<1>::new(key.as_bytes().to_vec().into()); + + let hash1 = algo.encode(data1.as_bytes().to_vec()).unwrap(); + let hash2 = algo.encode(data2.as_bytes().to_vec()).unwrap(); + + assert_ne!(hash1, hash2); + } + + #[test] + fn test_key_not_equal() { + let data = "Hello, World!"; + let key1 = "key1"; + let key2 = "key2"; + + let algo1 = HmacSha512::<1>::new(key1.as_bytes().to_vec().into()); + let algo2 = HmacSha512::<1>::new(key2.as_bytes().to_vec().into()); + + let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); + let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); + + assert_ne!(hash1, hash2); + } + + #[test] + fn test_key_equal() { + let data = "Hello, World!"; + let key1 = "key"; + let key2 = "key"; + + let algo1 = HmacSha512::<1>::new(key1.as_bytes().to_vec().into()); + let algo2 = HmacSha512::<1>::new(key2.as_bytes().to_vec().into()); + + let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); + let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); + + assert_eq!(hash1, hash2); + } + + #[test] + fn test_n_equal() { + let data = "Hello, World!"; + let key = "key"; + + let algo1 = HmacSha512::<10>::new(key.as_bytes().to_vec().into()); + let algo2 = HmacSha512::<10>::new(key.as_bytes().to_vec().into()); + + + let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); + let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); + + assert_eq!(hash1, hash2); + } + + #[test] + fn test_n_not_equal() { + let data = "Hello, World!"; + let key = "key"; + + let algo1 = HmacSha512::<10>::new(key.as_bytes().to_vec().into()); + let algo2 = HmacSha512::<20>::new(key.as_bytes().to_vec().into()); + + let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); + let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); + + assert_ne!(hash1, hash2); + } +} From d0ab6fd58e37b7a438bdbe9ccc19fb1137a9e61e Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:35:58 +0000 Subject: [PATCH 02/10] chore: run formatter --- src/crypto/sha.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/crypto/sha.rs b/src/crypto/sha.rs index dad73f4..9f816a8 100644 --- a/src/crypto/sha.rs +++ b/src/crypto/sha.rs @@ -17,7 +17,6 @@ impl super::Encode, Vec> for Sha512 { } } - /// /// Type providing encoding functional to perform HMAC-SHA512 hashing /// @@ -64,8 +63,6 @@ impl std::fmt::Display for HmacSha512 { } } - - impl super::Encode, Vec> for HmacSha512 { type ReturnType = Result>; @@ -89,7 +86,7 @@ impl AssertGt0 for HmacSha512 { #[cfg(test)] mod tests { - //! + //! //! Testing HMAC-SHA512 encoding consists of 3 variables. //! 1. The input data //! 2. The Key @@ -136,7 +133,7 @@ mod tests { let algo1 = HmacSha512::<1>::new(key1.as_bytes().to_vec().into()); let algo2 = HmacSha512::<1>::new(key2.as_bytes().to_vec().into()); - + let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); @@ -166,7 +163,6 @@ mod tests { let algo1 = HmacSha512::<10>::new(key.as_bytes().to_vec().into()); let algo2 = HmacSha512::<10>::new(key.as_bytes().to_vec().into()); - let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); From b1e80e9728db7a61d0adbe962b3d85eda0ca14a8 Mon Sep 17 00:00:00 2001 From: Nishant Joshi Date: Mon, 5 Feb 2024 19:29:42 +0530 Subject: [PATCH 03/10] chore: fix clippy errors --- benches/hashing.rs | 9 ++++++--- src/crypto/sha.rs | 18 ++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/benches/hashing.rs b/benches/hashing.rs index b5edc4a..2a8d727 100644 --- a/benches/hashing.rs +++ b/benches/hashing.rs @@ -24,7 +24,6 @@ pub fn criterion_hmac_sha512(c: &mut Criterion) { .collect::>() .into(); - const_iter!( algo, { @@ -32,14 +31,18 @@ pub fn criterion_hmac_sha512(c: &mut Criterion) { (1..ITERATION).for_each(|po| { let max: u64 = (2_u64).pow(po); let value = (0..max).map(|_| rand::random::()).collect::>(); - let hashed = algo.encode(value.clone()).unwrap(); + let hashed = algo.encode(value.clone()).expect("Failed while hashing"); group.throughput(criterion::Throughput::Bytes(max)); group.bench_with_input( criterion::BenchmarkId::from_parameter(format!("{}", max)), &value, |b, value| { b.iter(|| { - black_box(algo.encode(black_box(value.clone())).unwrap() == hashed) + black_box( + algo.encode(black_box(value.clone())) + .expect("Failed while hashing") + == hashed, + ) }) }, ); diff --git a/src/crypto/sha.rs b/src/crypto/sha.rs index dad73f4..85edaf6 100644 --- a/src/crypto/sha.rs +++ b/src/crypto/sha.rs @@ -17,7 +17,6 @@ impl super::Encode, Vec> for Sha512 { } } - /// /// Type providing encoding functional to perform HMAC-SHA512 hashing /// @@ -29,7 +28,7 @@ impl super::Encode, Vec> for Sha512 { /// /// let data = "Hello, World!"; /// let key = "key"; -/// let algo = HmacSha512::<1>::new(key.as_bytes().to_vec()); +/// let algo = HmacSha512::<1>::new(key.as_bytes().to_vec().into()); /// let hash = algo.encode(data.as_bytes().to_vec()).unwrap(); /// /// ``` @@ -42,7 +41,7 @@ impl super::Encode, Vec> for Sha512 { /// use tartarus::crypto::Encode; /// /// let key = "key"; -/// let algo = HmacSha512::<0>::new(key.as_bytes().to_vec()); +/// let algo = HmacSha512::<0>::new(key.as_bytes().to_vec().into()); /// /// /// ``` @@ -52,7 +51,8 @@ pub struct HmacSha512(masking::Secret>); impl HmacSha512 { pub fn new(key: masking::Secret>) -> Self { - _ = ::VALID; + #[allow(clippy::let_unit_value)] + let _ = ::VALID; Self(key) } @@ -64,13 +64,11 @@ impl std::fmt::Display for HmacSha512 { } } - - impl super::Encode, Vec> for HmacSha512 { type ReturnType = Result>; fn encode(&self, input: Vec) -> Self::ReturnType> { - let key = hmac::Key::new(ring::hmac::HMAC_SHA512, &self.0.peek()); + let key = hmac::Key::new(ring::hmac::HMAC_SHA512, self.0.peek()); let first = hmac::sign(&key, &input); let signature = (0..=(N - 1)).fold(first, |input, _| hmac::sign(&key, input.as_ref())); @@ -89,7 +87,8 @@ impl AssertGt0 for HmacSha512 { #[cfg(test)] mod tests { - //! + #![allow(clippy::unwrap_used, clippy::expect_used)] + //! //! Testing HMAC-SHA512 encoding consists of 3 variables. //! 1. The input data //! 2. The Key @@ -136,7 +135,7 @@ mod tests { let algo1 = HmacSha512::<1>::new(key1.as_bytes().to_vec().into()); let algo2 = HmacSha512::<1>::new(key2.as_bytes().to_vec().into()); - + let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); @@ -166,7 +165,6 @@ mod tests { let algo1 = HmacSha512::<10>::new(key.as_bytes().to_vec().into()); let algo2 = HmacSha512::<10>::new(key.as_bytes().to_vec().into()); - let hash1 = algo1.encode(data.as_bytes().to_vec()).unwrap(); let hash2 = algo2.encode(data.as_bytes().to_vec()).unwrap(); From 24a8267f96bd048516540d61f9591b1197060e15 Mon Sep 17 00:00:00 2001 From: Shankar Singh C Date: Tue, 6 Feb 2024 19:12:28 +0530 Subject: [PATCH 04/10] add fingerprint table and corresponding db interface for it --- Cargo.lock | 10 ++++ Cargo.toml | 2 + .../down.sql | 3 + .../up.sql | 8 +++ src/error/custom_error.rs | 14 +++++ src/error/transforms.rs | 32 ++++++++++ src/storage.rs | 23 ++++++++ src/storage/consts.rs | 10 ++++ src/storage/db.rs | 59 ++++++++++++++++++- src/storage/schema.rs | 16 ++++- src/storage/types.rs | 27 +++++++++ src/storage/utils.rs | 5 ++ 12 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 migrations/2024-02-06-064347_add-fingerprint-table/down.sql create mode 100644 migrations/2024-02-06-064347_add-fingerprint-table/up.sql create mode 100644 src/storage/consts.rs create mode 100644 src/storage/utils.rs diff --git a/Cargo.lock b/Cargo.lock index a042fda..cb067ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1786,6 +1786,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand", +] + [[package]] name = "nom" version = "7.1.3" @@ -2887,6 +2896,7 @@ dependencies = [ "josekit", "masking", "moka", + "nanoid", "once_cell", "rand", "ring 0.16.20", diff --git a/Cargo.toml b/Cargo.toml index 553217b..1d8ef13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,8 @@ moka = { version = "0.12.1", features = ["future"], optional = true } argh = "0.1.12" +nanoid = "0.4.0" + [dev-dependencies] rand = "0.8.5" criterion = "0.5.1" diff --git a/migrations/2024-02-06-064347_add-fingerprint-table/down.sql b/migrations/2024-02-06-064347_add-fingerprint-table/down.sql new file mode 100644 index 0000000..0fe2ed2 --- /dev/null +++ b/migrations/2024-02-06-064347_add-fingerprint-table/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE fingerprint; \ No newline at end of file diff --git a/migrations/2024-02-06-064347_add-fingerprint-table/up.sql b/migrations/2024-02-06-064347_add-fingerprint-table/up.sql new file mode 100644 index 0000000..aa326ec --- /dev/null +++ b/migrations/2024-02-06-064347_add-fingerprint-table/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here + +CREATE TABLE fingerprint ( + id SERIAL, + card_hash BYTEA UNIQUE NOT NULL, + card_fingerprint VARCHAR(255) NOT NULL, + PRIMARY KEY (card_hash) +); \ No newline at end of file diff --git a/src/error/custom_error.rs b/src/error/custom_error.rs index acfbcde..dd6eb44 100644 --- a/src/error/custom_error.rs +++ b/src/error/custom_error.rs @@ -46,6 +46,20 @@ pub enum HashDBError { UnknownError, } +#[derive(Debug, thiserror::Error)] +pub enum FingerprintDBError { + #[error("Error while connecting to database")] + DBError, + #[error("Error while finding element in the database")] + DBFilterError, + #[error("Error while inserting element in the database")] + DBInsertError, + #[error("Unpredictable error occurred")] + UnknownError, + #[error("Error while encoding data: {0}")] + EncodingError(&'static str), +} + pub trait NotFoundError { fn is_not_found(&self) -> bool; } diff --git a/src/error/transforms.rs b/src/error/transforms.rs index e7c1218..6bffa7c 100644 --- a/src/error/transforms.rs +++ b/src/error/transforms.rs @@ -80,6 +80,38 @@ impl<'a> From<&'a super::StorageError> for super::HashDBError { } } +error_transform!(super::StorageError => super::FingerprintDBError); +impl<'a> From<&'a super::StorageError> for super::FingerprintDBError { + fn from(value: &'a super::StorageError) -> Self { + match value { + super::StorageError::DBPoolError | super::StorageError::PoolClientFailure => { + Self::DBError + } + super::StorageError::FindError => Self::DBFilterError, + super::StorageError::DecryptionError + | super::StorageError::EncryptionError + | super::StorageError::DeleteError => Self::UnknownError, + super::StorageError::InsertError => Self::DBInsertError, + super::StorageError::NotFoundError => Self::DBFilterError, + } + } +} + +error_transform!(super::CryptoError => super::FingerprintDBError); +impl<'a> From<&'a super::CryptoError> for super::FingerprintDBError { + fn from(value: &'a super::CryptoError) -> Self { + match value { + super::CryptoError::SerdeJsonError(_) + | super::CryptoError::JWError(_) + | super::CryptoError::InvalidData(_) + | super::CryptoError::EncodingError(_) + | super::CryptoError::NotImplemented + | super::CryptoError::EncryptionError + | super::CryptoError::DecryptionError => Self::UnknownError, + } + } +} + error_transform!(super::CryptoError => super::HashDBError); impl<'a> From<&'a super::CryptoError> for super::HashDBError { fn from(value: &'a super::CryptoError) -> Self { diff --git a/src/storage.rs b/src/storage.rs index 2345e88..48603ef 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -22,6 +22,8 @@ pub mod caching; pub mod db; pub mod schema; pub mod types; +pub mod consts; +pub mod utils; pub trait State {} @@ -183,3 +185,24 @@ pub trait HashInterface { data_hash: Vec, ) -> Result>; } + +/// +/// Fingerprint: +/// +/// Interface providing functional to interface with the fingerprint table in database +#[async_trait::async_trait] +pub trait FingerprintInterface { + type Error; + + async fn find_by_card_hash( + &self, + card_hash: &[u8], + ) -> Result, ContainerError>; + + async fn insert_fingerprint( + &self, + card: types::Card, + hash_key: Secret, + ) -> Result>; +} + diff --git a/src/storage/consts.rs b/src/storage/consts.rs new file mode 100644 index 0000000..85757a0 --- /dev/null +++ b/src/storage/consts.rs @@ -0,0 +1,10 @@ +/// Characters to use for generating NanoID +pub(crate) const ALPHABETS: [char; 62] = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z', +]; + +/// Number of characters in a generated ID +pub const ID_LENGTH: usize = 20; \ No newline at end of file diff --git a/src/storage/db.rs b/src/storage/db.rs index 290f4cf..b2fdadb 100644 --- a/src/storage/db.rs +++ b/src/storage/db.rs @@ -5,11 +5,13 @@ use masking::ExposeInterface; use masking::Secret; use crate::crypto::aes::{generate_aes256_key, GcmAes256}; +use crate::crypto::sha::HmacSha512; +use crate::crypto::Encode; use crate::error::{self, ContainerError, ResultContainerExt}; use super::types::StorageDecryption; use super::types::StorageEncryption; -use super::{schema, types, LockerInterface, MerchantInterface, Storage}; +use super::{schema, types, utils, LockerInterface, MerchantInterface, Storage}; #[async_trait::async_trait] impl MerchantInterface for Storage { @@ -259,3 +261,58 @@ impl super::HashInterface for Storage { } } } + +#[async_trait::async_trait] +impl super::FingerprintInterface for Storage { + type Error = error::FingerprintDBError; + + async fn find_by_card_hash( + &self, + card_hash: &[u8], + ) -> Result, ContainerError> { + let mut conn = self.get_conn().await?; + + let output: Result<_, diesel::result::Error> = types::Fingerprint::table() + .filter(schema::fingerprint::card_hash.eq(card_hash)) + .get_result(&mut conn) + .await; + + match output { + Ok(inner) => Ok(Some(inner)), + Err(inner_err) => match inner_err { + diesel::result::Error::NotFound => Ok(None), + error => Err(error).change_error(error::StorageError::FindError)?, + }, + } + } + async fn insert_fingerprint( + &self, + card: types::Card, + hash_key: Secret, + ) -> Result> { + let algo = HmacSha512::<1>::new(hash_key.expose().into_bytes().into()); + let card_data = serde_json::to_vec(&card) + .change_error(error::FingerprintDBError::EncodingError("byte-vec"))?; + + let card_hash = algo.encode(card_data)?; + + let output = self.find_by_card_hash(&card_hash).await?; + match output { + Some(inner) => Ok(inner), + None => { + let mut conn = self.get_conn().await?; + let query = diesel::insert_into(types::Fingerprint::table()).values( + types::FingerprintTableNew { + card_hash, + card_fingerprint: utils::generate_id().into(), + }, + ); + + Ok(query + .get_result(&mut conn) + .await + .change_error(error::StorageError::InsertError)?) + } + } + } +} diff --git a/src/storage/schema.rs b/src/storage/schema.rs index fa3fe8c..22ead66 100644 --- a/src/storage/schema.rs +++ b/src/storage/schema.rs @@ -1,5 +1,14 @@ // @generated automatically by Diesel CLI. +diesel::table! { + fingerprint (card_hash) { + id -> Int4, + card_hash -> Bytea, + #[max_length = 255] + card_fingerprint -> Varchar, + } +} + diesel::table! { hash_table (hash_id) { id -> Int4, @@ -40,4 +49,9 @@ diesel::table! { } } -diesel::allow_tables_to_appear_in_same_query!(hash_table, locker, merchant,); +diesel::allow_tables_to_appear_in_same_query!( + fingerprint, + hash_table, + locker, + merchant, +); diff --git a/src/storage/types.rs b/src/storage/types.rs index 6e61e24..07fa73e 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -5,6 +5,7 @@ use diesel::{ sql_types, AsExpression, Identifiable, Insertable, Queryable, }; use masking::{ExposeInterface, Secret}; +use serde::Serialize; use crate::crypto::{self, Encryption}; @@ -97,6 +98,32 @@ pub struct HashTable { pub created_at: time::PrimitiveDateTime, } +#[derive(Debug, Clone, Identifiable, Queryable)] +#[diesel(table_name = schema::fingerprint)] +pub struct Fingerprint { + pub id: i32, + pub card_hash: Vec, + pub card_fingerprint: Secret, +} + +#[derive(Debug, Clone, Serialize)] +pub struct Card { + pub card_number: masking::StrongSecret, + name_on_card: Option, + card_exp_month: Option, + card_exp_year: Option, + card_brand: Option, + card_isin: Option, + nick_name: Option, +} + +#[derive(Debug, Insertable)] +#[diesel(table_name = schema::fingerprint)] +pub(super) struct FingerprintTableNew { + pub card_hash: Vec, + pub card_fingerprint: Secret, +} + #[derive(Debug, Insertable)] #[diesel(table_name = schema::hash_table)] pub(super) struct HashTableNew { diff --git a/src/storage/utils.rs b/src/storage/utils.rs new file mode 100644 index 0000000..e6f6459 --- /dev/null +++ b/src/storage/utils.rs @@ -0,0 +1,5 @@ +use crate::storage::consts; + +pub fn generate_id() -> String { + nanoid::nanoid!(20, &consts::ALPHABETS) +} \ No newline at end of file From 90ed3dbdca4fbcf6897b50a29edb24a18f54d767 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:03:20 +0000 Subject: [PATCH 05/10] chore: run formatter --- src/storage.rs | 3 +-- src/storage/consts.rs | 2 +- src/storage/schema.rs | 7 +------ src/storage/utils.rs | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index 48603ef..ec5eb37 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -19,10 +19,10 @@ use masking::{PeekInterface, Secret}; #[cfg(feature = "caching")] pub mod caching; +pub mod consts; pub mod db; pub mod schema; pub mod types; -pub mod consts; pub mod utils; pub trait State {} @@ -205,4 +205,3 @@ pub trait FingerprintInterface { hash_key: Secret, ) -> Result>; } - diff --git a/src/storage/consts.rs b/src/storage/consts.rs index 85757a0..1bc3034 100644 --- a/src/storage/consts.rs +++ b/src/storage/consts.rs @@ -7,4 +7,4 @@ pub(crate) const ALPHABETS: [char; 62] = [ ]; /// Number of characters in a generated ID -pub const ID_LENGTH: usize = 20; \ No newline at end of file +pub const ID_LENGTH: usize = 20; diff --git a/src/storage/schema.rs b/src/storage/schema.rs index 22ead66..2f12336 100644 --- a/src/storage/schema.rs +++ b/src/storage/schema.rs @@ -49,9 +49,4 @@ diesel::table! { } } -diesel::allow_tables_to_appear_in_same_query!( - fingerprint, - hash_table, - locker, - merchant, -); +diesel::allow_tables_to_appear_in_same_query!(fingerprint, hash_table, locker, merchant,); diff --git a/src/storage/utils.rs b/src/storage/utils.rs index e6f6459..b2b6706 100644 --- a/src/storage/utils.rs +++ b/src/storage/utils.rs @@ -2,4 +2,4 @@ use crate::storage::consts; pub fn generate_id() -> String { nanoid::nanoid!(20, &consts::ALPHABETS) -} \ No newline at end of file +} From 565c4d607d96132e94a8d3ae0d93a33a32e5a9f2 Mon Sep 17 00:00:00 2001 From: Shankar Singh C Date: Wed, 7 Feb 2024 11:00:53 +0530 Subject: [PATCH 06/10] specify the length of nano id as consts --- src/storage/db.rs | 4 ++-- src/storage/utils.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/storage/db.rs b/src/storage/db.rs index b2fdadb..08f9c2e 100644 --- a/src/storage/db.rs +++ b/src/storage/db.rs @@ -11,7 +11,7 @@ use crate::error::{self, ContainerError, ResultContainerExt}; use super::types::StorageDecryption; use super::types::StorageEncryption; -use super::{schema, types, utils, LockerInterface, MerchantInterface, Storage}; +use super::{consts, schema, types, utils, LockerInterface, MerchantInterface, Storage}; #[async_trait::async_trait] impl MerchantInterface for Storage { @@ -304,7 +304,7 @@ impl super::FingerprintInterface for Storage { let query = diesel::insert_into(types::Fingerprint::table()).values( types::FingerprintTableNew { card_hash, - card_fingerprint: utils::generate_id().into(), + card_fingerprint: utils::generate_id(consts::ID_LENGTH).into(), }, ); diff --git a/src/storage/utils.rs b/src/storage/utils.rs index e6f6459..427b797 100644 --- a/src/storage/utils.rs +++ b/src/storage/utils.rs @@ -1,5 +1,5 @@ use crate::storage::consts; -pub fn generate_id() -> String { - nanoid::nanoid!(20, &consts::ALPHABETS) +pub fn generate_id(id_length: usize) -> String { + nanoid::nanoid!(id_length, &consts::ALPHABETS) } \ No newline at end of file From a21b1a218a6ecba341774af71133444f8260dde0 Mon Sep 17 00:00:00 2001 From: Shankar Singh C Date: Wed, 7 Feb 2024 13:25:36 +0530 Subject: [PATCH 07/10] use only card number to calculate card_hash --- src/storage.rs | 2 +- src/storage/db.rs | 2 +- src/storage/types.rs | 13 ++----------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index ec5eb37..e210985 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -201,7 +201,7 @@ pub trait FingerprintInterface { async fn insert_fingerprint( &self, - card: types::Card, + card: types::CardNumber, hash_key: Secret, ) -> Result>; } diff --git a/src/storage/db.rs b/src/storage/db.rs index 08f9c2e..86650e3 100644 --- a/src/storage/db.rs +++ b/src/storage/db.rs @@ -287,7 +287,7 @@ impl super::FingerprintInterface for Storage { } async fn insert_fingerprint( &self, - card: types::Card, + card: types::CardNumber, hash_key: Secret, ) -> Result> { let algo = HmacSha512::<1>::new(hash_key.expose().into_bytes().into()); diff --git a/src/storage/types.rs b/src/storage/types.rs index 07fa73e..a801544 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -5,7 +5,6 @@ use diesel::{ sql_types, AsExpression, Identifiable, Insertable, Queryable, }; use masking::{ExposeInterface, Secret}; -use serde::Serialize; use crate::crypto::{self, Encryption}; @@ -106,16 +105,8 @@ pub struct Fingerprint { pub card_fingerprint: Secret, } -#[derive(Debug, Clone, Serialize)] -pub struct Card { - pub card_number: masking::StrongSecret, - name_on_card: Option, - card_exp_month: Option, - card_exp_year: Option, - card_brand: Option, - card_isin: Option, - nick_name: Option, -} +#[derive(serde::Serialize, Debug)] +pub struct CardNumber(masking::StrongSecret); #[derive(Debug, Insertable)] #[diesel(table_name = schema::fingerprint)] From d77232a3e6d5b9d147b79b4198336d04e62ea1ba Mon Sep 17 00:00:00 2001 From: Shankar Singh C Date: Wed, 7 Feb 2024 18:02:18 +0530 Subject: [PATCH 08/10] address pr comments --- .../2024-02-06-064347_add-fingerprint-table/up.sql | 2 +- src/error/custom_error.rs | 4 ++-- src/error/transforms.rs | 6 +++--- src/storage.rs | 2 +- src/storage/db.rs | 6 ++---- src/storage/schema.rs | 9 +++++++-- src/storage/types.rs | 14 ++++++++++---- 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/migrations/2024-02-06-064347_add-fingerprint-table/up.sql b/migrations/2024-02-06-064347_add-fingerprint-table/up.sql index aa326ec..e429bc3 100644 --- a/migrations/2024-02-06-064347_add-fingerprint-table/up.sql +++ b/migrations/2024-02-06-064347_add-fingerprint-table/up.sql @@ -3,6 +3,6 @@ CREATE TABLE fingerprint ( id SERIAL, card_hash BYTEA UNIQUE NOT NULL, - card_fingerprint VARCHAR(255) NOT NULL, + card_fingerprint VARCHAR(64) NOT NULL, PRIMARY KEY (card_hash) ); \ No newline at end of file diff --git a/src/error/custom_error.rs b/src/error/custom_error.rs index dd6eb44..3ba86a1 100644 --- a/src/error/custom_error.rs +++ b/src/error/custom_error.rs @@ -56,8 +56,8 @@ pub enum FingerprintDBError { DBInsertError, #[error("Unpredictable error occurred")] UnknownError, - #[error("Error while encoding data: {0}")] - EncodingError(&'static str), + #[error("Error while encoding data")] + EncodingError, } pub trait NotFoundError { diff --git a/src/error/transforms.rs b/src/error/transforms.rs index 6bffa7c..06994f7 100644 --- a/src/error/transforms.rs +++ b/src/error/transforms.rs @@ -87,12 +87,12 @@ impl<'a> From<&'a super::StorageError> for super::FingerprintDBError { super::StorageError::DBPoolError | super::StorageError::PoolClientFailure => { Self::DBError } - super::StorageError::FindError => Self::DBFilterError, + super::StorageError::FindError + | super::StorageError::NotFoundError => Self::DBFilterError, super::StorageError::DecryptionError | super::StorageError::EncryptionError | super::StorageError::DeleteError => Self::UnknownError, super::StorageError::InsertError => Self::DBInsertError, - super::StorageError::NotFoundError => Self::DBFilterError, } } } @@ -104,10 +104,10 @@ impl<'a> From<&'a super::CryptoError> for super::FingerprintDBError { super::CryptoError::SerdeJsonError(_) | super::CryptoError::JWError(_) | super::CryptoError::InvalidData(_) - | super::CryptoError::EncodingError(_) | super::CryptoError::NotImplemented | super::CryptoError::EncryptionError | super::CryptoError::DecryptionError => Self::UnknownError, + super::CryptoError::EncodingError(_) => Self::EncodingError, } } } diff --git a/src/storage.rs b/src/storage.rs index e210985..1f71eca 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -189,7 +189,7 @@ pub trait HashInterface { /// /// Fingerprint: /// -/// Interface providing functional to interface with the fingerprint table in database +/// Interface providing functions to interface with the fingerprint table in database #[async_trait::async_trait] pub trait FingerprintInterface { type Error; diff --git a/src/storage/db.rs b/src/storage/db.rs index 86650e3..b2b97aa 100644 --- a/src/storage/db.rs +++ b/src/storage/db.rs @@ -291,10 +291,8 @@ impl super::FingerprintInterface for Storage { hash_key: Secret, ) -> Result> { let algo = HmacSha512::<1>::new(hash_key.expose().into_bytes().into()); - let card_data = serde_json::to_vec(&card) - .change_error(error::FingerprintDBError::EncodingError("byte-vec"))?; - let card_hash = algo.encode(card_data)?; + let card_hash = algo.encode(card.into_bytes())?; let output = self.find_by_card_hash(&card_hash).await?; match output { @@ -303,7 +301,7 @@ impl super::FingerprintInterface for Storage { let mut conn = self.get_conn().await?; let query = diesel::insert_into(types::Fingerprint::table()).values( types::FingerprintTableNew { - card_hash, + card_hash: card_hash.into(), card_fingerprint: utils::generate_id(consts::ID_LENGTH).into(), }, ); diff --git a/src/storage/schema.rs b/src/storage/schema.rs index 2f12336..c06d350 100644 --- a/src/storage/schema.rs +++ b/src/storage/schema.rs @@ -4,7 +4,7 @@ diesel::table! { fingerprint (card_hash) { id -> Int4, card_hash -> Bytea, - #[max_length = 255] + #[max_length = 64] card_fingerprint -> Varchar, } } @@ -49,4 +49,9 @@ diesel::table! { } } -diesel::allow_tables_to_appear_in_same_query!(fingerprint, hash_table, locker, merchant,); +diesel::allow_tables_to_appear_in_same_query!( + fingerprint, + hash_table, + locker, + merchant, +); diff --git a/src/storage/types.rs b/src/storage/types.rs index a801544..cf5a2c5 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -4,7 +4,7 @@ use diesel::{ serialize::ToSql, sql_types, AsExpression, Identifiable, Insertable, Queryable, }; -use masking::{ExposeInterface, Secret}; +use masking::{ExposeInterface, PeekInterface, Secret}; use crate::crypto::{self, Encryption}; @@ -101,17 +101,23 @@ pub struct HashTable { #[diesel(table_name = schema::fingerprint)] pub struct Fingerprint { pub id: i32, - pub card_hash: Vec, + pub card_hash: Secret>, pub card_fingerprint: Secret, } -#[derive(serde::Serialize, Debug)] +#[derive( Debug, serde::Deserialize)] pub struct CardNumber(masking::StrongSecret); +impl CardNumber { + pub fn into_bytes(self) -> Vec { + self.0.peek().clone().into_bytes() + } +} + #[derive(Debug, Insertable)] #[diesel(table_name = schema::fingerprint)] pub(super) struct FingerprintTableNew { - pub card_hash: Vec, + pub card_hash: Secret>, pub card_fingerprint: Secret, } From 704a36207f5bebe9a24c7af63df00a1f24008502 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:40:27 +0000 Subject: [PATCH 09/10] chore: run formatter --- src/error/transforms.rs | 5 +++-- src/storage/schema.rs | 7 +------ src/storage/types.rs | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/error/transforms.rs b/src/error/transforms.rs index 06994f7..5d382f3 100644 --- a/src/error/transforms.rs +++ b/src/error/transforms.rs @@ -87,8 +87,9 @@ impl<'a> From<&'a super::StorageError> for super::FingerprintDBError { super::StorageError::DBPoolError | super::StorageError::PoolClientFailure => { Self::DBError } - super::StorageError::FindError - | super::StorageError::NotFoundError => Self::DBFilterError, + super::StorageError::FindError | super::StorageError::NotFoundError => { + Self::DBFilterError + } super::StorageError::DecryptionError | super::StorageError::EncryptionError | super::StorageError::DeleteError => Self::UnknownError, diff --git a/src/storage/schema.rs b/src/storage/schema.rs index c06d350..700dd50 100644 --- a/src/storage/schema.rs +++ b/src/storage/schema.rs @@ -49,9 +49,4 @@ diesel::table! { } } -diesel::allow_tables_to_appear_in_same_query!( - fingerprint, - hash_table, - locker, - merchant, -); +diesel::allow_tables_to_appear_in_same_query!(fingerprint, hash_table, locker, merchant,); diff --git a/src/storage/types.rs b/src/storage/types.rs index cf5a2c5..6158f97 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -105,7 +105,7 @@ pub struct Fingerprint { pub card_fingerprint: Secret, } -#[derive( Debug, serde::Deserialize)] +#[derive(Debug, serde::Deserialize)] pub struct CardNumber(masking::StrongSecret); impl CardNumber { From 3346be4c1f1a7dd168a60ca7e5b24ce572dd17f8 Mon Sep 17 00:00:00 2001 From: Shankar Singh C Date: Wed, 7 Feb 2024 19:46:55 +0530 Subject: [PATCH 10/10] address pr comments --- src/storage.rs | 2 +- src/storage/db.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index 1f71eca..75ece9d 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -196,7 +196,7 @@ pub trait FingerprintInterface { async fn find_by_card_hash( &self, - card_hash: &[u8], + card_hash: Secret<&[u8]>, ) -> Result, ContainerError>; async fn insert_fingerprint( diff --git a/src/storage/db.rs b/src/storage/db.rs index b2b97aa..c24ca7b 100644 --- a/src/storage/db.rs +++ b/src/storage/db.rs @@ -268,7 +268,7 @@ impl super::FingerprintInterface for Storage { async fn find_by_card_hash( &self, - card_hash: &[u8], + card_hash: Secret<&[u8]>, ) -> Result, ContainerError> { let mut conn = self.get_conn().await?; @@ -294,7 +294,7 @@ impl super::FingerprintInterface for Storage { let card_hash = algo.encode(card.into_bytes())?; - let output = self.find_by_card_hash(&card_hash).await?; + let output = self.find_by_card_hash(Secret::new(&card_hash)).await?; match output { Some(inner) => Ok(inner), None => {