diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -1719,6 +1719,7 @@ "http 0.2.12", "once_cell", "rand 0.8.5", + "regex", "reqwest", "serde", "serde_json", diff --git a/native/native_rust_library/Cargo.lock b/native/native_rust_library/Cargo.lock --- a/native/native_rust_library/Cargo.lock +++ b/native/native_rust_library/Cargo.lock @@ -335,6 +335,7 @@ "hex", "once_cell", "rand", + "regex", "serde", "serde_json", "sha2", @@ -1587,14 +1588,14 @@ [[package]] name = "regex" -version = "1.10.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1608,13 +1609,13 @@ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", ] [[package]] @@ -1625,9 +1626,9 @@ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "remove_dir_all" diff --git a/native/native_rust_library/Cargo.toml b/native/native_rust_library/Cargo.toml --- a/native/native_rust_library/Cargo.toml +++ b/native/native_rust_library/Cargo.toml @@ -18,7 +18,7 @@ argon2 = { version = "0.5.1", features = ["std"] } grpc_clients = { path = "../../shared/grpc_clients" } base64 = "0.21" -regex = "1.10" +regex = "1.10.3" [target.'cfg(target_os = "android")'.dependencies] backup_client = { path = "../../shared/backup_client", default-features = false, features = [ diff --git a/services/backup/Cargo.toml b/services/backup/Cargo.toml --- a/services/backup/Cargo.toml +++ b/services/backup/Cargo.toml @@ -18,6 +18,7 @@ "blob-client", "aws", "grpc_clients", + "crypto" ] } once_cell = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/services/identity/Cargo.toml b/services/identity/Cargo.toml --- a/services/identity/Cargo.toml +++ b/services/identity/Cargo.toml @@ -17,6 +17,7 @@ "aws", "grpc_clients", "blob-client", + "crypto" ] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -45,7 +45,10 @@ validate_remove_reserved_username_message, }; use crate::siwe::{ - is_valid_ethereum_address, parse_and_verify_siwe_message, SocialProof, + parse_and_verify_siwe_message, SocialProof, +}; +use comm_lib::{ + crypto, }; use crate::token::{AccessTokenData, AuthType}; pub use crate::grpc_services::protos::unauth::identity_client_service_server::{ @@ -110,7 +113,7 @@ debug!("Received registration request for: {}", message.username); if !is_valid_username(&message.username) - || is_valid_ethereum_address(&message.username) + || crypto::siwe::is_valid_ethereum_address(&message.username) { return Err(tonic::Status::invalid_argument( tonic_status_messages::INVALID_USERNAME, diff --git a/services/identity/src/siwe.rs b/services/identity/src/siwe.rs --- a/services/identity/src/siwe.rs +++ b/services/identity/src/siwe.rs @@ -4,7 +4,6 @@ aws::ddb::types::AttributeValue, database::{AttributeExtractor, AttributeMap, TryFromAttribute}, }; -use regex::Regex; use siwe::{Message, VerificationOpts}; use time::OffsetDateTime; use tonic::Status; @@ -55,11 +54,6 @@ Ok(siwe_message) } -pub fn is_valid_ethereum_address(candidate: &str) -> bool { - let ethereum_address_regex = Regex::new(r"^0x[a-fA-F0-9]{40}$").unwrap(); - ethereum_address_regex.is_match(candidate) -} - #[derive(derive_more::Constructor, Clone)] pub struct SocialProof { pub message: String, @@ -107,46 +101,6 @@ use super::*; - #[test] - fn test_valid_ethereum_address() { - assert!(is_valid_ethereum_address( - "0x1234567890123456789012345678901234567890" - ),); - assert!(is_valid_ethereum_address( - "0xABCDEF123456789012345678901234567890ABCD" - )); - assert!(is_valid_ethereum_address( - "0xabcdef123456789012345678901234567890abcd" - )); - } - - #[allow(clippy::bool_assert_comparison)] - #[test] - fn test_invalid_ethereum_address() { - // Shorter than 42 characters - assert_eq!( - is_valid_ethereum_address("0x12345678901234567890123456789012345678"), - false - ); - // Longer than 42 characters - assert_eq!( - is_valid_ethereum_address("0x123456789012345678901234567890123456789012"), - false - ); - // Missing 0x prefix - assert_eq!( - is_valid_ethereum_address("1234567890123456789012345678901234567890"), - false - ); - // Contains invalid characters - assert_eq!( - is_valid_ethereum_address("0x1234567890GHIJKL9012345678901234567890"), - false - ); - // Empty string - assert_eq!(is_valid_ethereum_address(""), false); - } - #[test] fn test_social_proof_ddb_format() { let message = "foo"; diff --git a/shared/comm-lib/Cargo.toml b/shared/comm-lib/Cargo.toml --- a/shared/comm-lib/Cargo.toml +++ b/shared/comm-lib/Cargo.toml @@ -43,6 +43,7 @@ hex = { workspace = true } uuid = { workspace = true, features = ["v4"] } sha2 = { workspace = true } +regex = { workspace = true } # aws dependencies aws-config = { workspace = true, optional = true } aws-sdk-dynamodb = { workspace = true, optional = true } diff --git a/shared/comm-lib/src/crypto/mod.rs b/shared/comm-lib/src/crypto/mod.rs --- a/shared/comm-lib/src/crypto/mod.rs +++ b/shared/comm-lib/src/crypto/mod.rs @@ -1,2 +1,3 @@ /// AES 256 GCM encryption and decryption. pub mod aes256; +pub mod siwe; diff --git a/shared/comm-lib/src/crypto/siwe.rs b/shared/comm-lib/src/crypto/siwe.rs new file mode 100644 --- /dev/null +++ b/shared/comm-lib/src/crypto/siwe.rs @@ -0,0 +1,52 @@ +use regex::Regex; + +pub fn is_valid_ethereum_address(candidate: &str) -> bool { + let ethereum_address_regex = Regex::new(r"^0x[a-fA-F0-9]{40}$").unwrap(); + ethereum_address_regex.is_match(candidate) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_valid_ethereum_address() { + assert!(is_valid_ethereum_address( + "0x1234567890123456789012345678901234567890" + ),); + assert!(is_valid_ethereum_address( + "0xABCDEF123456789012345678901234567890ABCD" + )); + assert!(is_valid_ethereum_address( + "0xabcdef123456789012345678901234567890abcd" + )); + } + + #[allow(clippy::bool_assert_comparison)] + #[test] + fn test_invalid_ethereum_address() { + // Shorter than 42 characters + assert_eq!( + is_valid_ethereum_address("0x12345678901234567890123456789012345678"), + false + ); + // Longer than 42 characters + assert_eq!( + is_valid_ethereum_address("0x123456789012345678901234567890123456789012"), + false + ); + // Missing 0x prefix + assert_eq!( + is_valid_ethereum_address("1234567890123456789012345678901234567890"), + false + ); + // Contains invalid characters + assert_eq!( + is_valid_ethereum_address("0x1234567890GHIJKL9012345678901234567890"), + false + ); + // Empty string + assert_eq!(is_valid_ethereum_address(""), false); + } +}