Database columns encryption#102
Conversation
WalkthroughThis update introduces cryptographic utilities for encrypting and decrypting identity keys using Argon2id and ChaCha20Poly1305, with a global cache for derived keys. Public API changes include new error variants, updated order methods for encrypted pubkey access, new prelude constants and re-exports, and an added optional admin password field in the user struct. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Order
participant CryptoUtils
participant Cache
User->>Order: get_master_buyer_pubkey(password)
Order->>CryptoUtils: decrypt_data(encrypted_pubkey, password)
CryptoUtils->>Cache: check for derived key (password+salt)
alt Key in cache
Cache-->>CryptoUtils: return cached key
else Key not in cache
CryptoUtils->>CryptoUtils: derive key with Argon2id
CryptoUtils->>Cache: store derived key
end
CryptoUtils->>Order: return decrypted pubkey or error
Order-->>User: return decrypted pubkey or error
Possibly related PRs
Suggested reviewers
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. Note ⚡️ Faster reviews with cachingCodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/error.rs (1)
94-95: Consider adding specific error subtypes for better error handling.The current implementation uses string messages for encryption/decryption errors, which makes programmatic error handling challenging.
Consider defining more specific error subtypes for common encryption failures:
pub enum EncryptionErrorKind { KeyDerivationFailed, EncryptionFailed, InvalidInput, Other(String), } // Then use it in ServiceError: EncryptionError(EncryptionErrorKind), DecryptionError(EncryptionErrorKind),src/order.rs (3)
198-204: Misleading comment & redundant base-64 round-tripComment says “Enecode salt from base64 to bytes” but the code actually encodes raw bytes into base-64 again (
encode_b64). Consider simplifying:let salt = Salt::new(salt)?; // consumes raw bytes directlyAt minimum, fix the comment to avoid future confusion.
240-247: Missing Associated Data (AAD) reduces integrity guarantees
cipher.encrypt(&nonce, plaintext)signs only the ciphertext. If you have any contextual metadata (e.g., user id, column name, version), pass it as AAD so tampering with those fields is detectable.cipher.encrypt(&nonce, aead::Payload { msg: idkey.as_bytes(), aad: b"user_id_column_v1", // example })Even a constant string thwarts several classes of swapping/re-ordering attacks.
254-268: Asynchronous wrapper adds no value; removeasync
store_encryptedis markedasyncbut performs no.awaitexcept for its own signature, meaning every call spawns a needless future.-pub async fn store_encrypted( +pub fn store_encrypted(This avoids surprising blocking points and simplifies call-sites.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
Cargo.toml(2 hunks)src/error.rs(2 hunks)src/message.rs(1 hunks)src/order.rs(2 hunks)src/user.rs(2 hunks)
🔇 Additional comments (4)
Cargo.toml (2)
3-3: Version bump looks appropriate for the feature addition.Version incremented from 0.6.37 to 0.6.38, which is suitable for adding encryption functionality.
39-42: Cryptographic dependencies properly chosen for the encryption requirement.The added dependencies support a robust encryption approach:
- argon2: Strong password hashing algorithm for key derivation
- chacha20poly1305: Modern authenticated encryption
- base64: For encoding encrypted binary data
- secrecy: For handling sensitive strings securely
These are well-maintained libraries with good security properties.
src/message.rs (1)
433-433: Import reordering has no functional impact.This is a minor change that just reorders imports in the test module with no functional impact.
src/error.rs (1)
94-95: Error variants properly added for encryption functionality.The encryption and decryption error variants are appropriately added to handle potential failures in cryptographic operations, with clear error messaging.
Also applies to: 152-153
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
src/order.rs (3)
187-196:⚠️ Potential issueMissing input validation before processing
The code splits
encrypted_byteswithout first checking if it's long enough, which could panic if the input is malformed (shorter than NONCE_SIZE + SALT_SIZE).Add length validation before splitting:
// Decode the encrypted data from base64 to bytes let encrypted_bytes = BASE64_STANDARD .decode(&data) .map_err(|_| ServiceError::DecryptionError("Error decoding encrypted data".to_string()))?; +// Validate input length before processing +if encrypted_bytes.len() < NONCE_SIZE + SALT_SIZE { + return Err(ServiceError::DecryptionError( + "Invalid encrypted data: too short for nonce and salt".to_string(), + )); +} // Split the encrypted data into nonce and data let (nonce, data) = encrypted_bytes.split_at(NONCE_SIZE);
213-219:⚠️ Potential issueArgon2 parameters are below recommended security levels
Current parameters use only 16KiB of memory and minimum time cost, which are far below OWASP recommendations for password hashing (≥64MiB, ≥3 iterations).
Increase parameters to improve security against brute force attacks:
let params = Params::new( - 16 * 1024, // 16 KiB - Params::MIN_T_COST, // 1 + 64 * 1024 * 1024, // 64 MiB + 3, // time cost of 3 Params::DEFAULT_P_COST * 2, Some(Params::DEFAULT_OUTPUT_LEN), )These stronger parameters significantly slow down offline attacks without noticeably impacting performance when the key is cached.
70-75:⚠️ Potential issueHash collisions can corrupt data silently
The
make_cache_keyfunction uses a non-cryptographicDefaultHasherand returns a 64-bit hash, which has insufficient collision resistance for cryptographic applications. A collision would return the wrong encryption key silently, leading to corrupted data rather than a clear error.Replace with a cryptographic hash function with a larger output space:
-fn make_cache_key(password: &str, salt: &[u8]) -> u64 { - let mut hasher = DefaultHasher::new(); - password.hash(&mut hasher); - salt.hash(&mut hasher); - hasher.finish() -} +use blake3; + +fn make_cache_key(password: &str, salt: &[u8]) -> [u8; 32] { + let mut input = Vec::with_capacity(password.len() + salt.len()); + input.extend_from_slice(password.as_bytes()); + input.extend_from_slice(salt); + *blake3::hash(&input).as_bytes() +}This change requires updating the SimpleCache implementation to use
[u8; 32]as the key type instead ofu64.
🧹 Nitpick comments (2)
src/order.rs (2)
176-255: Consider extracting shared key derivation logicThe key derivation logic is duplicated between
decrypt_dataandstore_encryptedfunctions, which could lead to inconsistencies if one is updated but not the other.Consider extracting the common logic into a separate function:
fn derive_key(password: &str, salt: &SaltString) -> Result<[u8; 32], ServiceError> { let cache_key = make_cache_key(password, salt.as_str().as_bytes()); let mut cache = KEY_CACHE .write() .map_err(|e| ServiceError::DecryptionError(format!("Error in key cache: {}", e)))?; if let Some(cached_key) = cache.get(cache_key) { return Ok(cached_key); } let params = Params::new( 64 * 1024 * 1024, // 64 MiB 3, // time cost Params::DEFAULT_P_COST, Some(Params::DEFAULT_OUTPUT_LEN), ) .map_err(|e| ServiceError::DecryptionError(format!("Error creating Argon2 params: {}", e)))?; let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); let password_hash = argon2 .hash_password(password.as_bytes(), salt) .map_err(|e| ServiceError::DecryptionError(format!("Error hashing password: {}", e)))?; let key = password_hash.hash .ok_or_else(|| ServiceError::DecryptionError("No hash generated".to_string()))?; let key_bytes = key.as_bytes(); if key_bytes.len() != 32 { return Err(ServiceError::DecryptionError( format!("Key length is {} bytes, expected 32", key_bytes.len()), )); } let mut key_array = [0u8; 32]; key_array.copy_from_slice(key_bytes); cache.put(cache_key, key_array); Ok(key_array) }This would simplify both functions and ensure consistent behavior.
Also applies to: 258-327
201-201: Error handling discards original error informationThe error handling for
SaltString::encode_b64discards the original error message, which could make debugging more difficult.Include the original error in the message:
let salt = SaltString::encode_b64(salt) - .map_err(|_| ServiceError::DecryptionError("Error decoding salt".to_string()))?; + .map_err(|e| ServiceError::DecryptionError(format!("Error decoding salt: {}", e)))?;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/order.rs(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.283Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.141Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
🔇 Additional comments (3)
src/order.rs (3)
29-30: Excellent use of RwLock for cache accessUsing RwLock instead of Mutex for the KEY_CACHE is a good decision as it allows multiple parallel readers, reducing contention in high-throughput scenarios. This is especially important since the cache is accessed during both encryption and decryption operations.
48-56: Good LRU implementation with position updatingThe cache implementation correctly updates the position of accessed keys in the LRU order, helping ensure that frequently used keys remain in the cache even under memory pressure.
323-325: Good use of Base64 encoding for ciphertextThe implementation correctly encodes the binary ciphertext as Base64 to make it suitable for storage in text-based database columns, which is a good practice for handling binary data in database systems.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (5)
src/order.rs (5)
70-75:⚠️ Potential issueHash collisions in make_cache_key can lead to security issues.
Using a 64-bit DefaultHasher for cryptographic purposes is risky as hash collisions could silently return the wrong key, leading to corrupted ciphertext rather than an error.
Consider implementing the safer approach from the previous review:
-fn make_cache_key(password: &str, salt: &[u8]) -> u64 { - let mut hasher = DefaultHasher::new(); - password.hash(&mut hasher); - salt.hash(&mut hasher); - hasher.finish() -} +type CacheKey = blake3::Hash; // 256-bit + +fn make_cache_key(password: &str, salt: &[u8]) -> CacheKey { + blake3::hash([password.as_bytes(), salt].concat().as_slice()) +}
214-219:⚠️ Potential issueArgon2 parameters are below recommended security levels.
The current settings use DEFAULT_M_COST (16 KiB by default) of RAM and MIN_T_COST (1) iteration, which falls below OWASP's recommendations for password hashing (≥64 MiB, ≥3 iterations).
Consider increasing the memory and time costs:
let params = Params::new( - Params::DEFAULT_M_COST, - Params::MIN_T_COST, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost Params::DEFAULT_P_COST * 2, Some(Params::DEFAULT_OUTPUT_LEN), )
275-276:⚠️ Potential issueUnwrap on user input can cause panic.
Using
unwrap()on the result ofSaltString::encode_b64can panic if the input salt is invalid, creating a stability risk in production.Replace with proper error handling:
// Salt generation let salt = if let Some(salt) = salt { - SaltString::encode_b64(salt.as_bytes()).unwrap() + SaltString::encode_b64(salt.as_bytes()) + .map_err(|e| ServiceError::EncryptionError(format!("Error encoding salt: {}", e)))? } else { SaltString::generate(&mut OsRng) };
300-300:⚠️ Potential issueUnwrap on hash value can cause panic.
Using
unwrap()onpassword_hash.hashcan panic if hash generation fails, creating a stability risk in production.Replace with proper error handling:
-let key = password_hash.hash.unwrap(); +let key = password_hash.hash + .ok_or_else(|| ServiceError::EncryptionError("Failed to generate hash".to_string()))?;
193-196: 💡 Verification agent🧩 Analysis chain
Missing input validation before splitting encrypted data.
While you've added error handling for conversion to arrays, you should validate input length before processing to prevent panics with malformed input.
Add proper length validation before processing:
let encrypted_bytes = BASE64_STANDARD .decode(&data) .map_err(|_| ServiceError::DecryptionError("Error decoding encrypted data".to_string()))?; + // Validate input length before processing + if encrypted_bytes.len() < NONCE_SIZE + SALT_SIZE { + return Err(ServiceError::DecryptionError( + "Invalid encrypted data: too short for nonce and salt".to_string(), + )); + } // Split the encrypted data into nonce and data let (nonce, data) = encrypted_bytes.split_at(NONCE_SIZE);
🏁 Script executed:
#!/bin/bash # Check if there are any length validations before splitting data rg -B 5 -A 10 'split_at' src/order.rsLength of output: 959
Add length checks before splitting encrypted data
To prevent panics when decrypting malformed input, ensure you validate
encrypted_bytes.len()before bothsplit_atcalls insrc/order.rs.Locations to update:
- Around the first
split_at(NONCE_SIZE)(before line ~193)- Around the subsequent
split_at(SALT_SIZE)(before line ~196)Suggested diff:
let encrypted_bytes = BASE64_STANDARD .decode(&data) .map_err(|_| ServiceError::DecryptionError("Error decoding encrypted data".to_string()))?; + // Validate that we have at least nonce + salt + if encrypted_bytes.len() < NONCE_SIZE + SALT_SIZE { + return Err(ServiceError::DecryptionError( + "Invalid encrypted data: too short for nonce and salt".to_string(), + )); + } // Split out the nonce let (nonce_bytes, rest) = encrypted_bytes.split_at(NONCE_SIZE); let nonce: [u8; NONCE_SIZE] = nonce_bytes .try_into() .map_err(|_| ServiceError::DecryptionError("Error converting nonce to array".to_string()))?; - let (salt, ciphertext) = data.split_at(SALT_SIZE); + // Split out the salt + let (salt_bytes, ciphertext) = rest.split_at(SALT_SIZE); // Decode salt from base64 to bytes let salt = SaltString::encode_b64(salt_bytes) .map_err(|_| ServiceError::DecryptionError("Error decoding salt".to_string()))?;This ensures neither split will panic even if the input is truncated.
🧹 Nitpick comments (3)
src/order.rs (3)
176-328: Consider refactoring common code between crypto functions.Both
decrypt_dataandstore_encryptedshare similar parameter handling, key derivation, and validation logic. Extracting these common operations into helper functions would improve maintainability and reduce duplication.Consider creating helper functions for:
- Key derivation from password and salt
- Parameter validation
- Consistent error handling
Example refactoring:
fn derive_key(password: &str, salt: &SaltString) -> Result<[u8; 32], ServiceError> { // Create Argon2 parameters let params = Params::new( 64 * 1024, // 64 MiB (in KiB) 3, // time-cost Params::DEFAULT_P_COST, // parallelism Some(Params::DEFAULT_OUTPUT_LEN), // 32 bytes ) .map_err(|e| ServiceError::EncryptionError(format!("Error creating params: {}", e)))?; let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); // Hash the password with the salt let password_hash = argon2 .hash_password(password.as_bytes(), salt) .map_err(|e| ServiceError::EncryptionError(format!("Error hashing password: {}", e)))?; let key = password_hash.hash .ok_or_else(|| ServiceError::EncryptionError("Failed to generate hash".to_string()))?; let key_bytes = key.as_bytes(); if key_bytes.len() != 32 { return Err(ServiceError::EncryptionError( format!("Key length is {} bytes, expected 32", key_bytes.len()), )); } let mut key_array = [0u8; 32]; key_array.copy_from_slice(key_bytes); Ok(key_array) }
176-255: Add documentation with security guarantees and limitations.While the implementation includes good comments, it lacks formal documentation about the security guarantees provided, limitations, and appropriate usage patterns.
Add comprehensive documentation:
/// Decrypt data that was previously encrypted with `store_encrypted`. /// /// # Security /// - Uses Argon2id for key derivation (memory-hard password hashing) /// - Uses ChaCha20Poly1305 for authenticated encryption /// - Provides confidentiality, integrity, and authentication /// /// # Limitations /// - Not resistant to side-channel attacks /// - Security depends on the strength of the provided password /// /// # Parameters /// - `data`: Base64-encoded encrypted data (containing nonce, salt, and ciphertext) /// - `password`: Optional password for decryption /// /// # Returns /// - `Ok(String)`: Decrypted data as a UTF-8 string /// - `Err(ServiceError)`: If decryption fails pub fn decrypt_data(data: String, password: Option<&SecretString>) -> Result<String, ServiceError> {Also applies to: 257-328
319-321: Consider using a structured format for encrypted data.The current implementation concatenates binary data (nonce, salt, ciphertext) without clear separation, which could make future format changes difficult.
Consider either:
- Adding version and length prefixes for forward compatibility
- Using a structured format like PASETO for the encrypted payload
Example with simple versioning:
// Combine nonce and ciphertext - let mut encrypted = Vec::with_capacity(NONCE_SIZE + SALT_SIZE + ciphertext.len()); - encrypted.extend_from_slice(&nonce); - encrypted.extend_from_slice(salt_decoded); - encrypted.extend_from_slice(&ciphertext); + let mut encrypted = Vec::with_capacity(1 + NONCE_SIZE + SALT_SIZE + ciphertext.len()); + encrypted.push(1); // Version byte for future format changes + encrypted.extend_from_slice(&nonce); + encrypted.extend_from_slice(salt_decoded); + encrypted.extend_from_slice(&ciphertext);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/order.rs(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.283Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.141Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
🧬 Code Graph Analysis (1)
src/order.rs (1)
src/error.rs (2)
fmt(107-112)fmt(116-155)
🔇 Additional comments (2)
src/order.rs (2)
29-30: Good implementation of RwLock for KEY_CACHE.The RwLock implementation aligns with the best practice shared in your feedback, which helps reduce contention in the encryption/decryption cache. This is an improvement over using a standard Mutex since it allows multiple readers to access the cache simultaneously.
192-198: 🛠️ Refactor suggestionMitigate timing attacks on the decryption process.
The current implementation might be vulnerable to timing attacks, as attackers could potentially infer information based on the time it takes to process valid vs. invalid ciphertexts.
Consider implementing constant-time operations where possible and adding a random delay for invalid inputs to mitigate timing attacks:
// Decrypt the data let decrypted = cipher .decrypt(&nonce.into(), ciphertext) - .map_err(|e| ServiceError::DecryptionError(e.to_string()))?; + .map_err(|e| { + // Add a small random delay to mitigate timing attacks + let mut rng = OsRng; + let delay = rng.gen_range(5..15); + std::thread::sleep(std::time::Duration::from_millis(delay)); + ServiceError::DecryptionError(e.to_string()) + })?;This helps protect against attackers trying to differentiate between authentication failures and other types of failures based on response time.
Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/order.rs (3)
70-75:⚠️ Potential issueHash collision risk in cache key generation
The current implementation of
make_cache_keyuses a 64-bitDefaultHasherwhich has a relatively high collision probability for cryptographic applications. A collision would cause a different derived key to be returned, potentially leading to corrupted data instead of an error.Consider using a cryptographic hash function with a larger output:
-fn make_cache_key(password: &str, salt: &[u8]) -> u64 { - let mut hasher = DefaultHasher::new(); - password.hash(&mut hasher); - salt.hash(&mut hasher); - hasher.finish() -} +type CacheKey = [u8; 32]; // 256-bit key + +fn make_cache_key(password: &str, salt: &[u8]) -> CacheKey { + // Use blake3 or similar for cryptographic security + let mut hasher = blake3::Hasher::new(); + hasher.update(password.as_bytes()); + hasher.update(salt); + let hash = hasher.finalize(); + *hash.as_bytes() +}The SimpleCache implementation would also need to be updated to use this new key type.
193-197:⚠️ Potential issueMissing input validation before processing ciphertext
The code doesn't validate if the encrypted_bytes are long enough before trying to split them. If a malicious or corrupted input is shorter than NONCE_SIZE + SALT_SIZE, this could panic at runtime.
Add proper length validation before splitting:
+ // Validate input length before processing + if encrypted_bytes.len() < NONCE_SIZE + SALT_SIZE { + return Err(ServiceError::DecryptionError( + "Invalid encrypted data: too short for nonce and salt".to_string(), + )); + } + // Split the encrypted data into nonce and data let (nonce, data) = encrypted_bytes.split_at(NONCE_SIZE); let nonce: [u8; NONCE_SIZE] = nonce.try_into().map_err(|_| { ServiceError::DecryptionError("Error converting nonce to array".to_string()) })?;
214-219:⚠️ Potential issueArgon2 parameters are below recommended security standards
The current Argon2 parameters use minimal memory (DEFAULT_M_COST = 16 KiB) and iterations (MIN_T_COST = 1), which falls far below OWASP's recommendations for password hashing (≥64 MiB, ≥3 iterations).
Strengthen the parameters for better security against brute-force attacks:
// Hash the password let params = Params::new( - Params::DEFAULT_M_COST, - Params::MIN_T_COST, + 64 * 1024, // 64 MiB memory cost + 3, // 3 iterations Params::DEFAULT_P_COST * 2, Some(Params::DEFAULT_OUTPUT_LEN), )This makes offline attacks significantly more expensive while maintaining reasonable performance due to your cache.
🧹 Nitpick comments (3)
src/order.rs (3)
273-276: Fixed salt in test mode could lead to unexpected behaviorUsing a fixed salt in test mode is fine for deterministic testing, but it's worth adding a comment explaining this choice and its security implications.
Add an explanatory comment:
// Salt generation #[cfg(test)] + // Fixed salt for deterministic test results - NEVER use this in production let salt = SaltString::encode_b64(b"1H/aaYsf8&asduA0").unwrap(); #[cfg(not(test))] let salt = SaltString::generate(&mut OsRng);
188-190: Improve error message clarity for Base64 decoding failuresThe error message for Base64 decoding failures doesn't include the specific error, making debugging more difficult.
Enhance the error message to include the original error:
// Decode the encrypted data from base64 to bytes let encrypted_bytes = BASE64_STANDARD .decode(&data) - .map_err(|_| ServiceError::DecryptionError("Error decoding encrypted data".to_string()))?; + .map_err(|e| ServiceError::DecryptionError(format!("Error decoding encrypted data: {}", e)))?;
524-525: Generic error mapping masks specific decryption issuesThe
get_master_buyer_pubkeymethod maps any decryption error toServiceError::InvalidPubkey. This makes it harder to distinguish between an actual invalid pubkey and decryption failures.Consider preserving the original error information:
pub fn get_master_buyer_pubkey( &self, password: Option<&SecretString>, ) -> Result<String, ServiceError> { if let Some(pk) = self.master_buyer_pubkey.as_ref() { - decrypt_data(pk.clone(), password).map_err(|_| ServiceError::InvalidPubkey) + decrypt_data(pk.clone(), password).map_err(|e| { + // For backward compatibility, log the error first + log::error!("Decryption error for master_buyer_pubkey: {}", e); + ServiceError::InvalidPubkey + }) } else { Err(ServiceError::InvalidPubkey) } }Apply the same pattern to
get_master_seller_pubkeyon line 535.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/order.rs(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.304Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.163Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
🔇 Additional comments (3)
src/order.rs (3)
30-30: RwLock is a good choice for the KEY_CACHEUsing RwLock instead of Mutex for the KEY_CACHE is a solid choice that will reduce contention in scenarios with multiple readers, allowing for better performance in a server environment.
176-255: Properly implemented encryption/decryption functionalityThe implementation of
decrypt_datauses strong cryptographic primitives (Argon2id for key derivation, ChaCha20Poly1305 for authenticated encryption) with a sensible approach to caching derived keys to improve performance. The function handles the absence of a password gracefully and has appropriate error handling.Once the security issues mentioned in other comments are addressed (input validation, stronger Argon2 parameters, and avoiding hash collisions), this will be a robust implementation.
568-600: Improved privacy model with encryption supportThe refactoring of
is_full_privacy_orderto support encrypted master keys is well-implemented. The function now returns the actual decrypted keys instead of just boolean flags, which provides more flexibility for callers while maintaining the privacy model.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/order.rs (1)
220-226: 🛠️ Refactor suggestionIncrease Argon2 memory and time costs for stronger password hashing
The current Argon2 parameters use the default memory cost and minimum time cost, which might not provide sufficient protection against offline brute-force attacks for sensitive encryption keys.
let params = Params::new( - Params::DEFAULT_M_COST, - Params::MIN_T_COST, - Params::DEFAULT_P_COST * 2, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost + Params::DEFAULT_P_COST, // parallelism (1 thread) Some(Params::DEFAULT_OUTPUT_LEN), ) .map_err(|_| ServiceError::DecryptionError("Error Argon2 creating params".to_string()))?;
🧹 Nitpick comments (2)
src/order.rs (2)
280-285: Avoid fixed salt in test codeUsing a fixed salt in test code is convenient but could lead to accidental security issues if mistakenly used in non-test code. Consider using a deterministic but still random approach for tests.
#[cfg(test)] -let salt = SaltString::encode_b64(b"1H/aaYsf8&asduA0").unwrap(); +let salt = { + // Still deterministic but taken from a test-specific source + let mut test_rng = rand_chacha::ChaCha8Rng::seed_from_u64(12345); + SaltString::generate(&mut test_rng) +};
175-176: Extract encryption parameters as constantsThe salt and nonce sizes are defined separately in both encryption functions. Extract these to module-level constants to ensure consistency and make future updates easier.
+// Cryptographic constants +const SALT_SIZE: usize = 16; +const NONCE_SIZE: usize = 12; + /// Decrypt an identity key from the database pub fn decrypt_data(data: String, password: Option<&SecretString>) -> Result<String, ServiceError> { - // Salt size and nonce size - const SALT_SIZE: usize = 16; - const NONCE_SIZE: usize = 12;And similarly for the store_encrypted function.
Also applies to: 266-270
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Cargo.toml(2 hunks)src/order.rs(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Cargo.toml
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.304Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.163Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
🔇 Additional comments (8)
src/order.rs (8)
28-29: Good use of RwLock instead of Mutex for the key cacheUsing RwLock instead of Mutex is appropriate here since the cache will likely experience more reads than writes. This reduces contention as multiple readers can access the cache simultaneously.
33-35: Great choice using blake3 Hash for the cache keyUsing blake3::Hash (256-bit) for the cache key is an excellent security choice that prevents collision attacks, which could otherwise lead to incorrect key derivation. This implementation follows the advice from previous review comments.
36-69: Well-implemented LRU cache with proper eviction strategyThe SimpleCache implementation provides a proper LRU (Least Recently Used) eviction strategy with a fixed size of 50 entries. The implementation correctly updates the access order in both get() and put() operations.
72-74: Good implementation of cache key generationThe make_cache_key function creates a cryptographically secure hash by concatenating the password and salt before hashing, which is the correct approach for creating unique keys while preserving security properties.
532-533: Good update of master pubkey retrieval to use decryptionThe get_master_buyer_pubkey method has been correctly updated to decrypt the stored pubkey using the decrypt_data function, properly handling the optional password parameter.
543-544: Good update of master pubkey retrieval to use decryptionThe get_master_seller_pubkey method has been correctly updated to decrypt the stored pubkey using the decrypt_data function, properly handling the optional password parameter.
576-604: Privacy-preserving implementation of full privacy checkThe is_full_privacy_order method effectively determines if users are operating in full privacy mode by comparing decrypted master keys with the order keys, without compromising security. The function returns the normal (non-private) keys for further processing when appropriate.
211-214:Details
✅ Verification successful
Good use of RwLock for reducing cache contention
The code correctly uses the RwLock's write() method when accessing the cache, which matches the RwLock pattern established in the learnings. This helps reduce contention in the encryption/decryption cache.
🏁 Script executed:
#!/bin/bash # Check if RwLock is used consistently across the codebase rg -A 2 "RwLock" src/Length of output: 321
RwLock usage verified as consistent and correct
A search across
src/confirms thatRwLockis declared and used only insrc/order.rs. Thewrite()call at lines 211–214 appropriately acquires mutable access to the cache, matching the established pattern and reducing contention. No further changes are needed.
There was a problem hiding this comment.
Actionable comments posted: 0
🔭 Outside diff range comments (2)
src/order.rs (2)
777-777:⚠️ Potential issueReplace unwrap with safe error handling in fiat_amount method
The
fiat_amountmethod still usesunwrap()which could cause panics on invalid input. Consider replacing with safer alternatives.pub fn fiat_amount(&self) -> String { if self.max_amount.is_some() { - format!("{}-{}", self.min_amount.unwrap(), self.max_amount.unwrap()) + format!("{}-{}", + self.min_amount.unwrap_or_default(), + self.max_amount.unwrap_or_default()) } else { self.fiat_amount.to_string() } }Or better yet, return a Result to propagate errors:
pub fn fiat_amount(&self) -> Result<String, ServiceError> { if self.max_amount.is_some() { let min = self.min_amount.ok_or_else(|| ServiceError::InvalidOrderData("Missing min_amount".to_string()))?; let max = self.max_amount.ok_or_else(|| ServiceError::InvalidOrderData("Missing max_amount".to_string()))?; Ok(format!("{}-{}", min, max)) } else { Ok(self.fiat_amount.to_string()) } }
787-789:⚠️ Potential issueReplace unwrap in From implementation
The
From<Order>implementation usesunwrap()which could cause panics on invalid input. Consider replacing with safe error handling.impl From<Order> for SmallOrder { fn from(order: Order) -> Self { let id = Some(order.id); - let kind = Kind::from_str(&order.kind).unwrap(); - let status = Status::from_str(&order.status).unwrap(); + let kind = Kind::from_str(&order.kind).unwrap_or(Kind::Buy); + let status = Status::from_str(&order.status).unwrap_or(Status::Active); // ... } }Or define a TryFrom implementation that can return errors:
impl TryFrom<Order> for SmallOrder { type Error = ServiceError; fn try_from(order: Order) -> Result<Self, Self::Error> { let id = Some(order.id); let kind = Kind::from_str(&order.kind) .map_err(|_| ServiceError::InvalidOrderKind)?; let status = Status::from_str(&order.status) .map_err(|_| ServiceError::InvalidOrderStatus)?; // ... Ok(Self { /* ... */ }) } }
♻️ Duplicate comments (2)
src/order.rs (2)
314-320:⚠️ Potential issueInconsistent Argon2 parameters between functions
The Argon2 parameters in
store_encryptedshould match those indecrypt_datato ensure compatibility and consistent security. Apply the same security enhancement here as recommended above.let params = Params::new( - Params::DEFAULT_M_COST, - Params::MIN_T_COST, - Params::DEFAULT_P_COST * 2, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost + Params::DEFAULT_P_COST, // parallelism (1 thread) Some(Params::DEFAULT_OUTPUT_LEN), ) .map_err(|_| ServiceError::EncryptionError("Error creating params".to_string()))?;
241-247:⚠️ Potential issueSecurity concern: Argon2 parameters are too weak
The current parameters use only 16 MiB of RAM (DEFAULT_M_COST) and a single iteration (MIN_T_COST), which is below OWASP recommendations for password hashing (≥ 64 MiB, ≥ 3 iterations).
let params = Params::new( - Params::DEFAULT_M_COST, - Params::MIN_T_COST, - Params::DEFAULT_P_COST * 2, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost + Params::DEFAULT_P_COST, // parallelism (1 thread) Some(Params::DEFAULT_OUTPUT_LEN), ) .map_err(|_| ServiceError::DecryptionError("Error Argon2 creating params".to_string()))?;These stronger parameters significantly slow down offline attacks without noticeably impacting performance when the key is cached.
🧹 Nitpick comments (1)
src/order.rs (1)
302-305: Consider adding a seed parameter for testingUsing a hardcoded salt in test mode is good, but consider making it configurable via a function parameter to support different test scenarios.
pub fn store_encrypted( idkey: &str, password: Option<&SecretString>, + test_salt: Option<&[u8]>, ) -> Result<String, ServiceError> { // ... // Salt generation #[cfg(test)] - let salt = SaltString::encode_b64(b"1H/aaYsf8&asduA0").unwrap(); + let salt = if let Some(test_salt) = test_salt { + SaltString::encode_b64(test_salt) + .map_err(|_| ServiceError::EncryptionError("Error encoding test salt".to_string()))? + } else { + SaltString::encode_b64(b"1H/aaYsf8&asduA0") + .map_err(|_| ServiceError::EncryptionError("Error encoding default test salt".to_string()))? + }; #[cfg(not(test))] let salt = SaltString::generate(&mut OsRng);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Cargo.toml(2 hunks)src/order.rs(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Cargo.toml
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (3)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.304Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.163Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:191-196
Timestamp: 2025-05-07T22:06:39.013Z
Learning: User is working on removing unwrap() calls in application code (especially in src/order.rs) but acknowledges that some unwrap() calls in test sections are acceptable.
🔇 Additional comments (9)
src/order.rs (9)
29-30: Good use of RwLock for the key cacheAs mentioned in our previous discussions, using RwLock instead of Mutex reduces contention in the encryption/decryption cache. I see you've implemented this recommendation well.
29-30: Excellent use of SecretBox for the cacheWrapping the SimpleCache with SecretBox provides an additional layer of security by protecting sensitive cryptographic material in memory.
35-36: Great choice switching to blake3 for cache keysUsing blake3 instead of DefaultHasher eliminates the risk of silent collisions that could lead to corrupted ciphertext, as we discussed previously.
73-89: Good implementation of secure memory clearingThe Zeroize trait implementation and Drop handler ensure that sensitive cryptographic material is properly cleared from memory when no longer needed, which is an essential security practice.
211-216: Excellent input validation before processingYou've added proper length validation before processing the encrypted data, which prevents potential panics from malformed input. This addresses our previous discussion about the importance of input validation.
219-222: Good error handling for try_into conversionYou've replaced the previous unwrap() call with proper error handling using map_err(), which is more robust and prevents panics on invalid input.
257-259: Good error handling for hash unwrappingYou've properly replaced unwrap() with ok_or_else() to handle the case when the hash is None, which prevents potential panics.
346-351: Good buffer allocation with capacityPreallocating the buffer with the right capacity is efficient and avoids multiple reallocations during assembly of the encrypted data.
597-625: Well-designed privacy modelThe approach of comparing master keys with order keys to determine if a user is in privacy mode is sensible and maintains backward compatibility.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/order.rs (2)
314-320: 🛠️ Refactor suggestionEnsure consistent Argon2 parameters between encryption and decryption
The Argon2 parameters should be identical in both encryption and decryption functions to ensure compatibility. Consider extracting these parameters to a constant or function to ensure they stay in sync.
let params = Params::new( - Params::DEFAULT_M_COST, - Params::MIN_T_COST, - Params::DEFAULT_P_COST * 2, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost + Params::DEFAULT_P_COST, // parallelism (1 thread) Some(Params::DEFAULT_OUTPUT_LEN), )
241-247: 💡 Verification agent🧩 Analysis chain
Consider increasing Argon2 memory and time costs
The current Argon2 parameters (
DEFAULT_M_COST,MIN_T_COST,DEFAULT_P_COST * 2) may not provide sufficient protection against dedicated attackers with hardware acceleration. Consider increasing the memory cost and time cost parameters to strengthen security, especially since the results are cached.let params = Params::new( - Params::DEFAULT_M_COST, - Params::MIN_T_COST, - Params::DEFAULT_P_COST * 2, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost + Params::DEFAULT_P_COST, // parallelism (1 thread) Some(Params::DEFAULT_OUTPUT_LEN), )
🌐 Web query:
What are the recommended Argon2 parameters for password hashing in 2024?💡 Result:
As of 2024, the recommended parameters for securely hashing passwords using Argon2 are as follows:
Variant: Use Argon2id, which combines the benefits of both Argon2i and Argon2d, offering protection against side-channel and GPU-based attacks. (rfc-editor.org)
Memory Cost (m): Allocate 2 GiB (2^21 KiB) of memory. This setting provides a robust defense against brute-force attacks by consuming significant memory resources. (rfc-editor.org)
Iterations (t): Perform 1 iteration. A single iteration balances security and performance, ensuring the hashing process is time-consuming enough to deter attackers without introducing excessive delays for legitimate users. (rfc-editor.org)
Parallelism (p): Utilize 4 lanes. This parameter defines the number of parallel computational chains, allowing the hashing process to leverage multiple CPU cores effectively. (rfc-editor.org)
Salt Length: Use a 16-byte (128-bit) salt. A longer salt enhances security by making precomputed attacks, such as rainbow table attacks, more difficult. (rfc-editor.org)
Tag Length: Generate a 32-byte (256-bit) tag. This provides a sufficient level of security for most applications. (rfc-editor.org)
These settings are recommended for general use cases, balancing security and performance. However, if your application operates in a memory-constrained environment, you might consider the following alternative:
Memory Cost (m): Allocate 64 MiB (2^16 KiB) of memory.
Iterations (t): Perform 3 iterations.
Parallelism (p): Utilize 4 lanes.
This configuration offers a reasonable level of security while being more suitable for systems with limited memory resources. (rfc-editor.org)
It's important to note that the optimal settings may vary based on your specific application requirements and hardware capabilities. Regularly reviewing and adjusting these parameters in response to advancements in hardware and attack techniques is essential to maintain robust password security.
Citations:
- 1: https://www.rfc-editor.org/rfc/rfc9106.html?utm_source=openai
- 2: https://www.rfc-editor.org/rfc/rfc9106.html?utm_source=openai
- 3: https://www.rfc-editor.org/rfc/rfc9106.html?utm_source=openai
- 4: https://www.rfc-editor.org/rfc/rfc9106.html?utm_source=openai
- 5: https://www.rfc-editor.org/rfc/rfc9106.html?utm_source=openai
- 6: https://www.rfc-editor.org/rfc/rfc9106.html?utm_source=openai
- 7: https://www.rfc-editor.org/rfc/rfc9106.html?utm_source=openai
I recommend bumping the Argon2 parameters to use a more robust, 2024-recommended setup—especially if these hashes are cached long-term. For a reasonable trade-off in a memory-constrained service, switch to:
let params = Params::new( - Params::DEFAULT_M_COST, // ~4 MiB - Params::MIN_T_COST, // 1 iteration - Params::DEFAULT_P_COST * 2, // 2 lanes + 64 * 1024, // 64 MiB (2^16 KiB) + 3, // 3 iterations + 4, // 4 lanes (parallelism) Some(Params::DEFAULT_OUTPUT_LEN), ) .map_err(|_| ServiceError::DecryptionError("Error Argon2 creating params".to_string()))?;• Consider also using the Argon2id variant if you aren’t already; it’s the current best practice per RFC 9106.
• If your environment can afford it, you may bump memory to 2 GiB (2^21 KiB) and drop iterations to 1 for maximum hardness against GPU attacks.
🧹 Nitpick comments (1)
src/order.rs (1)
196-204: Consider documenting encryption method and parametersThe current function comments are useful but brief. Consider adding more detailed documentation about the encryption scheme (ChaCha20Poly1305 with Argon2id key derivation), the format of the encrypted data (nonce + salt + ciphertext), and any other relevant details that would help developers understand and use these functions correctly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/order.rs(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (3)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.304Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.163Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:191-196
Timestamp: 2025-05-07T22:06:39.013Z
Learning: User is working on removing unwrap() calls in application code (especially in src/order.rs) but acknowledges that some unwrap() calls in test sections are acceptable.
🔇 Additional comments (7)
src/order.rs (7)
28-30: Good use of RwLock instead of Mutex for the key cacheThe implementation follows the previously discussed recommendation to use
RwLockinstead ofMutexfor the key cache. This will help reduce contention in concurrent scenarios, as the cache is accessed during both encryption and decryption operations.
35-37: Good choice using blake3 for cache keysUsing
blake3::Hashas the cache key type is an excellent choice, as it provides strong collision resistance with a 256-bit hash space. This properly addresses the collision concerns that would have existed with a simpleu64hash.
73-89: Proper memory security with Zeroize and Drop implementationsThe implementation of
ZeroizeandDroptraits ensures that sensitive cryptographic material is properly erased from memory when no longer needed. This is a critical security practice when dealing with encryption keys.
547-568: Good API design for decryption in get_master_*_pubkey methodsThe update of the
get_master_buyer_pubkeyandget_master_seller_pubkeymethods to accept an optional password parameter allows for flexible encryption/decryption based on user needs. This is a clean, backward-compatible API design.
597-625: Improved privacy model with encryption supportThe
is_full_privacy_orderfunction has been thoughtfully updated to support encryption, maintaining the privacy features while adding the ability to decrypt when needed. This implementation properly handles error cases and maintains the original privacy checking logic.
302-305: 🛠️ Refactor suggestionConsider more secure handling of test salt
Using a hardcoded salt for tests is reasonable, but the current implementation potentially allows unsafe unwrap() usage. Consider updating the test salt implementation to handle errors properly:
#[cfg(test)] -let salt = SaltString::encode_b64(b"1H/aaYsf8&asduA0").unwrap(); +let salt = SaltString::encode_b64(b"1H/aaYsf8&asduA0") + .map_err(|_| ServiceError::EncryptionError("Error encoding test salt".to_string()))?;Likely an incorrect or invalid review comment.
211-216:Details
❓ Verification inconclusive
Good input validation before processing encrypted data
Proper length validation is implemented before processing the encrypted data, which prevents potential panics from malformed input. This aligns with the best practice of validating inputs, especially with cryptographic operations.
🏁 Script executed:
#!/bin/bash # Check if there are any remaining unwrap() calls in the decrypt_data function rg "unwrap\(\)" src/order.rs --context=5 | grep -A 10 -B 10 "decrypt_data"Length of output: 74
🏁 Script executed:
#!/bin/bash # Display the decrypt_data function and check for any unwrap() calls within it rg -C50 "fn decrypt_data" src/order.rs | rg "unwrap("Length of output: 120
🏁 Script executed:
#!/bin/bash # Extract the decrypt_data function with context and search for literal unwrap( occurrences rg -C50 "fn decrypt_data" src/order.rs | rg -E "unwrap\\("Length of output: 456
Manual unwrap() usage verification required
I couldn’t locate any
unwrap()calls in thedecrypt_dataimplementation, but please double-check the entire function in src/order.rs to ensure no panics remain.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/order.rs (3)
195-216: Good implementation of input validation before processingThe
decrypt_datafunction properly validates input length before processing to prevent potential panics from malformed input. This is a best practice in cryptographic code handling user input, and it directly addresses the concern raised in a previous review comment.
326-332: 🛠️ Refactor suggestionEnsure consistent Argon2 parameters between encrypt and decrypt functions
The Argon2 parameters should be identical in both encryption and decryption functions to ensure compatibility. Please apply the same parameter improvements suggested for the decrypt_data function here as well.
let params = Params::new( - Params::DEFAULT_M_COST, - Params::DEFAULT_T_COST, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost Params::DEFAULT_P_COST * 2, Some(Params::DEFAULT_OUTPUT_LEN), )
241-247: 💡 Verification agent🧩 Analysis chain
Consider increasing Argon2 memory and time costs
The current Argon2 parameters are too weak for password-based encryption. The memory cost (
DEFAULT_M_COST) and time cost (DEFAULT_T_COST) should be increased to provide better protection against brute-force attacks.let params = Params::new( - Params::DEFAULT_M_COST, - Params::DEFAULT_T_COST, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost Params::DEFAULT_P_COST * 2, Some(Params::DEFAULT_OUTPUT_LEN), )This won't significantly impact performance since you're caching the derived keys.
🌐 Web query:
What are the recommended Argon2 parameters for password hashing?💡 Result:
The recommended Argon2 parameters for password hashing depend on your specific security needs, including the available memory, desired level of protection, and acceptable performance impact. However, general guidelines for Argon2 (especially Argon2id, which is recommended for password hashing) are:
Memory Cost: Allocate a significant amount of memory to resist GPU and ASIC attacks. Typical recommendations range from 64 MB to 1 GB or more for interactive logins, with higher memory settings for more secure environments.
Time Cost (Iterations): This is the number of iterations the algorithm performs. A value between 2 to 4 iterations is common, balancing between security and performance.
Parallelism: The number of threads or parallel lanes used. Usually set to the number of CPU cores or 1 for single-threaded environments.
Hash Length: The length of the resulting hash. 16 to 32 bytes is typical.
A common starting point example would be:
- Memory: 64 MiB
- Time cost: 3
- Parallelism: 4 (if you have 4 CPU cores)
- Hash length: 32 bytes
It is recommended to benchmark these parameters on your target system to ensure the performance is acceptable while maximizing the resource usage to slow down any attackers.
If you want, I can provide more concrete examples for specific environments or use cases.
Increase Argon2 work factors for stronger password-based encryption
The current defaults (
Params::DEFAULT_M_COST,Params::DEFAULT_T_COST) are on the low side for interactive logins. Benchmarks and community guidelines recommend:
- Memory cost: ≥ 64 MiB (64 × 1024 KiB)
- Time cost: 3 iterations
- Parallelism: match your CPU cores (e.g.
num_cpus::get() as u32)- Hash length: 32 bytes (the default)
Updating your code in src/order.rs (around lines 241–247) might look like:
let params = Params::new( - Params::DEFAULT_M_COST, - Params::DEFAULT_T_COST, - Params::DEFAULT_P_COST * 2, - Some(Params::DEFAULT_OUTPUT_LEN), + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost (iterations) + num_cpus::get() as u32, // parallelism (CPU cores) + Some(Params::DEFAULT_OUTPUT_LEN), // output length (32 bytes) ) .map_err(|_| ServiceError::DecryptionError("Error Argon2 creating params".to_string()))?;These values will dramatically increase the work required for offline attacks without a significant performance hit—especially since you cache derived keys. Don’t forget to benchmark on your target environment and adjust if necessary.
🧹 Nitpick comments (1)
src/order.rs (1)
38-71: Consider adding documentation to the SimpleCache implementationThe LRU cache implementation is well-structured, but it would benefit from documentation explaining its purpose and functionality, especially since it's handling sensitive cryptographic material.
+/// A simple LRU cache with fixed size for cryptographic keys +/// +/// This cache stores derived cryptographic keys indexed by a hash of the password and salt. +/// It has a fixed maximum size and evicts the least recently used entries when full. struct SimpleCache { map: HashMap<CacheKey, [u8; 32]>, order: VecDeque<CacheKey>, }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/order.rs(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (2)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.304Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.163Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
🧬 Code Graph Analysis (1)
src/order.rs (1)
src/error.rs (2)
fmt(107-112)fmt(116-155)
🔇 Additional comments (7)
src/order.rs (7)
28-30: Good implementation of the key cache with RwLock!The use of
RwLockinstead ofMutexfor theKEY_CACHEis an excellent choice for reducing contention in high-concurrency scenarios. This allows multiple concurrent reads while still protecting writes, which aligns well with the retrieved learning that emphasizes reducing contention in the encryption/decryption cache.
35-37: Excellent choice of using Blake3 for the cache key!Using a 256-bit
blake3::Hashfor cache keys is a significant security improvement compared to a standard hash function. This effectively eliminates collision risks when mapping(password, salt)pairs to cache keys.
73-89: Proper implementation of Zeroize for secure memory handling!The implementation of
Zeroizefor the cache ensures cryptographic key material is properly cleared from memory when no longer needed, which is a critical security practice.
91-94: Well-designed cache key generation functionThe
make_cache_keyfunction correctly combines password and salt to create a secure, collision-resistant key using Blake3, which aligns with cryptographic best practices.
560-569: Appropriately modified method to handle encrypted pubkeysThe
get_master_buyer_pubkeymethod has been correctly updated to accept an optional password parameter and return a decrypted String instead of a parsed PublicKey. This change enables the retrieval of encrypted identity keys while maintaining a consistent API.
571-580: Correctly implemented decryption for seller pubkeysSimilar to the buyer pubkey method, the
get_master_seller_pubkeymethod has been properly updated to handle encrypted keys.
609-637: Well-designed privacy check implementationThe
is_full_privacy_ordermethod correctly identifies when users are operating in privacy mode by comparing their master keys with order keys. The function properly handles errors and returns decrypted keys for normal (non-private) users.
… of privacy users
removed async definition from store_encrypted function change hash algo of cached keys to blake3 added a check on minimum length of encrypted keys, must be at least the sum of nonce and salt
…ossible leaks in debug channels.
restored defult time pass value of argon2 hash algo added correct management for timings of decryption - added an option for a fixed salt
171962e to
e20f514
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/rating.rs (1)
35-35: Consider preserving original error details for debuggingWhile using specific error types is good, the current implementation discards original error information with
map_err(|_| ServiceError::...). For debugging purposes, you might want to consider preserving this information, perhaps in logs or by including it in specific error variants where it doesn't risk exposing sensitive data.Also applies to: 40-40, 97-97, 102-102, 107-107, 112-112, 117-117
README.md (2)
3-3: Fix missing article in description
Add “the” to improve clarity in:- It serves as the foundation for building Mostro daemon. + It serves as the foundation for building the Mostro daemon.🧰 Tools
🪛 LanguageTool
[uncategorized] ~3-~3: You might be missing the article “the” here.
Context: ...t serves as the foundation for building Mostro daemon. ## Requirements - Rust 1.86.0...(AI_EN_LECTOR_MISSING_DETERMINER_THE)
17-17: Improve heading grammar and casing
Consider updating the heading for consistency and clarity:- ## Import prelude to use mostro core + ## Import the prelude to use Mostro Core
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
.github/workflows/rust.yml(1 hunks)CHANGELOG.md(1 hunks)Cargo.toml(2 hunks)README.md(1 hunks)cliff.toml(1 hunks)contributing.md(1 hunks)rust-toolchain.toml(1 hunks)src/error.rs(2 hunks)src/lib.rs(1 hunks)src/message.rs(5 hunks)src/order.rs(5 hunks)src/prelude.rs(1 hunks)src/rating.rs(4 hunks)src/user.rs(2 hunks)
✅ Files skipped from review due to trivial changes (6)
- rust-toolchain.toml
- src/lib.rs
- cliff.toml
- CHANGELOG.md
- contributing.md
- src/prelude.rs
🚧 Files skipped from review as they are similar to previous changes (5)
- src/user.rs
- src/error.rs
- Cargo.toml
- src/order.rs
- src/message.rs
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/rating.rs (2)
src/order.rs (4)
from_json(739-741)from_str(108-114)from_str(173-192)as_json(744-746)src/message.rs (6)
from_json(36-38)from_json(154-156)from_json(287-289)as_json(40-42)as_json(159-161)as_json(291-293)
🪛 LanguageTool
README.md
[uncategorized] ~3-~3: You might be missing the article “the” here.
Context: ...t serves as the foundation for building Mostro daemon. ## Requirements - Rust 1.86.0...
(AI_EN_LECTOR_MISSING_DETERMINER_THE)
🔇 Additional comments (12)
src/rating.rs (5)
4-5: Good addition of typed error handling with ServiceErrorThe introduction of
ServiceErroraligns with the security focus of this PR. Using specific error types rather than genericanyhowerrors improves error handling and avoids potentially leaking sensitive data through error messages.
34-36: Appropriate error type conversion in JSON deserializationThe change from generic error handling to using
ServiceError::MessageSerializationErrorprovides better error specificity and consistency. This aligns well with similar changes in other modules likemessage.rsandorder.rs.
39-41: Consistent error handling in serialization methodThe
as_jsonmethod now correctly returnsServiceError::MessageSerializationErrorfor serialization failures, maintaining consistency with other JSON serialization methods throughout the codebase.
78-93: Improved error specificity in tag parsingThe error handling for missing tag keys and values now uses the more specific
ServiceError::NostrErrorvariant with descriptive messages, which enhances security and debugging.
94-118: Consistent use of ParsingNumberError for numeric conversionsThe numeric parsing code now properly uses
ServiceError::ParsingNumberErroracross all field types (u64, f64, u8), providing consistent error handling for parsing failures..github/workflows/rust.yml (1)
19-20: Verify GitHub Action version pin
Usingdtolnay/rust-toolchain@1.86.0appears to pin the action itself to a non-existent tag matching the Rust toolchain version, rather than the actual action version. The recommended practice is to pin the action to its release tag (e.g.,v1), while letting it readrust-toolchain.tomlfor the Rust version.README.md (6)
7-8: Requirements section aligns with toolchain
Specifying “Rust 1.86.0 or later” and “Cargo (Rust’s package manager)” correctly reflects the CI and local toolchain update.
12-16: Features list is clear and comprehensive
The listed features accurately reflect the library’s capabilities and recent cryptographic enhancements.
19-21: Prelude import snippet is correct
The exampleuse mostro_core::prelude::*;matches the newly addedpreludemodule.
23-28: Contribute section is well-defined
Links to “good first issue” and the contributing guide properly onboard new contributors.
29-33: Documentation links are accurate
The protocol, English FAQ, and Spanish FAQ URLs correctly reference external docs.
34-36: License declaration is correct
The MIT license link is properly included.
…n modules of mostro-core
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/order.rs (2)
324-329: 🛠️ Refactor suggestionEnsure consistent parameters between encrypt and decrypt functions
The Argon2 parameters should be identical between your encryption and decryption functions to ensure compatibility. The same security concerns apply here as with the decrypt_data function.
Apply the same improved parameters here:
let params = Params::new( - Params::DEFAULT_M_COST, - Params::DEFAULT_T_COST, - Params::DEFAULT_P_COST * 2, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost + Params::DEFAULT_P_COST, // parallelism Some(Params::DEFAULT_OUTPUT_LEN), )
239-244: 🛠️ Refactor suggestionIncrease Argon2 memory and time costs for stronger password hashing
The current Argon2 parameters might not be sufficiently tuned for security. OWASP recommends higher memory usage and iterations for password hashing.
Increase the memory cost and time cost parameters for stronger security against offline attacks:
let params = Params::new( - Params::DEFAULT_M_COST, - Params::DEFAULT_T_COST, - Params::DEFAULT_P_COST * 2, + 64 * 1024, // 64 MiB (in KiB) + 3, // time-cost (iterations) + Params::DEFAULT_P_COST, // parallelism Some(Params::DEFAULT_OUTPUT_LEN), )These stronger parameters will significantly slow down offline attacks without noticeable impact when using the cache you've implemented.
🧹 Nitpick comments (3)
src/order.rs (3)
217-221: Consider using constant-time comparisons for cryptographic operationsWhen working with cryptographic material, it's best practice to use constant-time operations to prevent timing attacks.
While the current implementation is functionally correct, consider using a library like
subtlefor constant-time comparisons when validating cryptographic data:- let nonce: [u8; NONCE_SIZE] = nonce.try_into().map_err(|_| { - ServiceError::DecryptionError("Error converting nonce to array".to_string()) - })?; + let nonce: [u8; NONCE_SIZE] = nonce.try_into().map_err(|_| { + ServiceError::DecryptionError("Error converting nonce to array".to_string()) + })?; + + // When comparing MACs or other cryptographic values later + // use subtle::ConstantTimeEq::ct_eq instead of ==
194-282: Consider refactoring common cryptographic operations into a separate moduleThe decrypt_data and store_encrypted functions contain duplicated logic for key derivation and cryptographic operations.
Consider extracting the common cryptographic operations into a separate CryptoUtils module or struct to improve maintainability and reduce duplication:
// In a new file like src/crypto.rs or src/utils/crypto.rs use crate::prelude::*; // other imports... pub struct CryptoUtils; impl CryptoUtils { /// Derive a key from password and salt fn derive_key(password: &str, salt: &SaltString) -> Result<[u8; 32], ServiceError> { // Common key derivation logic } /// Encrypt data with the provided key pub fn encrypt(data: &[u8], key: &[u8; 32]) -> Result<(Vec<u8>, [u8; NONCE_SIZE]), ServiceError> { // Encryption logic } /// Decrypt data with the provided key pub fn decrypt(ciphertext: &[u8], nonce: &[u8; NONCE_SIZE], key: &[u8; 32]) -> Result<Vec<u8>, ServiceError> { // Decryption logic } // Then your public API functions that use these helpers: pub fn decrypt_data(data: String, password: Option<&SecretString>) -> Result<String, ServiceError> { // Use the helper functions } pub fn store_encrypted( idkey: &str, password: Option<&SecretString>, fixed_salt: Option<SaltString>, ) -> Result<String, ServiceError> { // Use the helper functions } }Also applies to: 296-367
1-24: Consider using external crates for common cryptographic patternsWhile your implementation is solid, there are established Rust crates for common cryptographic patterns that could simplify your code.
Consider using established libraries like
orionorringthat provide higher-level APIs for common cryptographic operations:// Example using orion for password-based encryption use orion::aead; use orion::pwhash; pub fn encrypt(data: &str, password: &str) -> Result<String, ServiceError> { let salt = pwhash::Salt::generate()?; let key = pwhash::Password::from_slice(password.as_bytes())? .derive_key(&salt, 3, 64 * 1024, 32)?; let sealed = aead::seal(&key, data.as_bytes())?; // Combine salt + sealed data and encode }These libraries often handle many of the security considerations like constant-time comparisons automatically.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
Cargo.toml(1 hunks)src/error.rs(4 hunks)src/message.rs(1 hunks)src/order.rs(4 hunks)src/prelude.rs(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/message.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- Cargo.toml
- src/error.rs
- src/prelude.rs
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (3)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.304Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.163Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:191-196
Timestamp: 2025-05-07T22:06:39.041Z
Learning: User is working on removing unwrap() calls in application code (especially in src/order.rs) but acknowledges that some unwrap() calls in test sections are acceptable.
🔇 Additional comments (10)
src/order.rs (10)
27-28: Excellent use of RwLock for the KEY_CACHEUsing RwLock instead of Mutex for the KEY_CACHE is a great choice to reduce contention in the encryption/decryption cache since it allows multiple concurrent readers. The SecretBox wrapper further enhances security by protecting the sensitive key data.
34-34: Good choice using blake3 for cache keysUsing blake3 hash (256-bit) for cache keys provides excellent collision resistance, which is critical for a cryptographic cache. This is much more secure than a simple 64-bit hash that might lead to silent key collisions.
36-69: Well-implemented LRU cache with fixed sizeThe SimpleCache implementation with a fixed size (MAX_CACHE_SIZE=50) and LRU eviction policy is appropriate for caching derived keys. The implementation correctly handles the ordering of keys for LRU eviction.
72-87: Great security practice with Zeroize implementationImplementing Zeroize for the SimpleCache ensures that sensitive cryptographic material is properly cleared from memory when no longer needed, preventing potential memory disclosure vulnerabilities.
90-92: Effective cache key generationUsing blake3 to hash the concatenation of password and salt is an effective way to generate unique cache keys while maintaining security. This approach prevents potential cache poisoning attacks.
209-214: Proper input validation before processingThe length validation before processing encrypted data is excellent defensive programming. This prevents potential panics from malformed input, addressing the previously identified issues with unwrap() calls.
255-258: Good error handling for Argon2 hash retrievalUsing
ok_or_elseinstead ofunwrap()to handle potential missing hash values is a good practice, preventing panics in production.
356-360: Good practice concatenating nonce, salt and ciphertextThe approach of concatenating the nonce, salt, and ciphertext in a specific order and including all necessary data for decryption is a good practice. This ensures that decryption can happen without external data dependencies.
557-567: Well-integrated decryption in pubkey getter methodsThe updated pubkey getter methods properly integrate the decryption functionality while maintaining backward compatibility by returning ServiceError on failure, which fits with the existing error handling pattern.
Also applies to: 569-578
604-628: Good privacy mode detection implementationThe updated is_full_privacy_order method correctly identifies privacy mode by comparing decrypted master pubkeys with the order pubkeys, returning the decrypted keys for normal users. This ensures proper handling of both privacy and normal mode users.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
src/crypto.rs (1)
51-65: Early‐return validation duplicates error stringsBoth key-length and salt-length checks return identical string literals from other sites. Consider extracting them into
consterror messages or usingformat!with the offending length to aid debugging.Small nitpick—no functional change required.
src/order.rs (3)
2-10: Several heavy cryptography imports are unused
Algorithm,Argon2,Params,PasswordHasher,Version, andAeadCoreare not referenced in this file.
Keeping them:
- emits warnings,
- slows compile times.
-use argon2::{ - password_hash::{rand_core::OsRng, Salt, SaltString}, - Algorithm, Argon2, Params, PasswordHasher, Version, -}; -use chacha20poly1305::{ - aead::{Aead, KeyInit}, - AeadCore, ChaCha20Poly1305, Key, -}; +use argon2::password_hash::{rand_core::OsRng, Salt, SaltString}; +use chacha20poly1305::{aead::{Aead, KeyInit}, ChaCha20Poly1305, Key};Trimming the list keeps the module lean.
🧰 Tools
🪛 GitHub Actions: Rust
[warning] 4-4: Unused imports:
Algorithm,Argon2,Params,PasswordHasher, andVersion.
[warning] 9-9: Unused import:
AeadCore.
49-57: Read–write contention insidegetBecause
getcurrently mutatesself.order, every read requires&mut self.
If you adopt the redesign above, consider:
- splitting LRU maintenance into a separate method,
- or using
parking_lot::Mutex+ interior mutability,- or replacing the whole cache with
dashmapwhich gives lock-free reads.This will materially improve throughput under high concurrency.
223-228:SaltString::encode_b64performs an encode, not a decodeThe comment says “decode salt from base64”, but the call encodes the raw
salt slice. The code is correct (derive_key expectsSaltString), the
documentation is misleading.- // Enecode salt from base64 to bytes + // Convert raw 16-byte salt into a SaltString (base64 encoded)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
Cargo.toml(1 hunks)src/crypto.rs(1 hunks)src/lib.rs(1 hunks)src/order.rs(4 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- Cargo.toml
🧰 Additional context used
🧠 Learnings (1)
src/order.rs (3)
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:30-69
Timestamp: 2025-04-26T09:46:57.304Z
Learning: The project uses RwLock instead of Mutex for the KEY_CACHE in src/order.rs to reduce contention in the encryption/decryption cache, as the cache is accessed during both encryption and decryption operations.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:189-196
Timestamp: 2025-04-26T09:41:59.163Z
Learning: When handling encrypted data or user input, always implement proper length validation before using unwrap() or other potentially panicking operations, especially in the decrypt_data() and store_encrypted() functions. The user acknowledged the importance of this practice and committed to implementing input validation in their cryptographic functions.
Learnt from: arkanoider
PR: MostroP2P/mostro-core#102
File: src/order.rs:191-196
Timestamp: 2025-05-07T22:06:39.041Z
Learning: User is working on removing unwrap() calls in application code (especially in src/order.rs) but acknowledges that some unwrap() calls in test sections are acceptable.
🪛 GitHub Actions: Rust
src/crypto.rs
[error] 93-93: Cannot find type SecretString in this scope. Consider importing secrecy::SecretString or use SaltString instead.
[error] 99-99: Cannot find type SecretString in this scope. Consider importing secrecy::SecretString or use SaltString instead.
[error] 88-88: Mismatched types: function decrypt implicitly returns () but expected Result<Vec<u8>, ServiceError>.
[error] 93-93: Mismatched types: function decrypt_data implicitly returns () but expected Result<String, ServiceError>.
[error] 101-101: Mismatched types: function store_encrypted implicitly returns () but expected Result<String, ServiceError>.
[warning] 4-4: Unused import: Salt.
src/order.rs
[warning] 4-4: Unused imports: Algorithm, Argon2, Params, PasswordHasher, and Version.
[warning] 9-9: Unused import: AeadCore.
🔇 Additional comments (1)
src/order.rs (1)
26-29:RwLock<SecretBox<…>>neuters read-parallelism & may not beSyncWrapping
SimpleCachein bothSecretBoxandRwLockyields:
- Every cache access requires a write lock (
write()), becauseSimpleCache::gettakes&mut self.
This serialises all look-ups and defeats the purpose ofRwLock.SecretBox<T>does not implementSyncunconditionally – putting it in a static can fail
to compile on some toolchains.Recommended redesign:
-pub struct SimpleCache { … } +#[derive(Zeroize)] +pub struct SimpleCache { … } -static KEY_CACHE: LazyLock<RwLock<SecretBox<SimpleCache>>> = - LazyLock::new(|| RwLock::new(SecretBox::new(Box::new(SimpleCache::new())))); +static KEY_CACHE: LazyLock<RwLock<SimpleCache>> = + LazyLock::new(|| RwLock::new(SimpleCache::new()));Then change
getto accept&mut selfonly when updating or add a separate
touchmethod so look-ups can happen under a read lock:fn get(&self, key: &CacheKey) -> Option<[u8; 32]> { … } // &selfThis restores concurrent reads, avoids extra heap allocation, and lets
Zeroizeclear memory on drop.
[ suggest_essential_refactor ]
- crypto.rs completed with helper functions for encryption-decryption - some chores with new constat exported directly from prelude - moved also cache logic for key in crypto.rs file
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
src/crypto.rs (2)
2-11: Unused imports causing warnings and potential confusionRemove unused imports and consider being more specific with imports to improve maintainability:
use crate::prelude::*; use argon2::{ - password_hash::{rand_core::OsRng, Salt, SaltString}, + password_hash::{rand_core::OsRng, SaltString}, Algorithm, Argon2, Params, PasswordHasher, Version, }; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; use chacha20poly1305::{ aead::{Aead, KeyInit}, - AeadCore, ChaCha20Poly1305, Key, + ChaCha20Poly1305, Key, }; -use secrecy::*; +use secrecy::SecretString;These imports aren't used in the code and cause compiler warnings. A more specific import for
secrecywould be better than the wildcard import.
97-102: Argon2 parameters are too weak for cryptographic securityThe current Argon2 parameters use default memory and time costs that are too low for secure password hashing:
pub fn derive_key(password: &str, salt: &SaltString) -> Result<Vec<u8>, ServiceError> { // Common key derivation logic let params = Params::new( - Params::DEFAULT_M_COST, - Params::DEFAULT_T_COST, - Params::DEFAULT_P_COST * 2, - Some(Params::DEFAULT_OUTPUT_LEN), + 64 * 1024, // 64 MiB in KiB + 3, // iterations + 1, // parallelism factor + Some(DERIVED_KEY_LENGTH as u32), )OWASP and RFC 9106 recommend at least 64 MiB memory cost and 3 or more iterations for password hashing to resist offline attacks. This change will strengthen security against brute-force attacks.
🧹 Nitpick comments (5)
src/crypto.rs (5)
56-65: Potential inefficiency in cache management logicThe cache implementation doesn't properly handle the case when a key already exists but the cache is full:
fn put(&mut self, key: CacheKey, value: [u8; 32]) { - if !self.map.contains_key(&key) && self.map.len() >= MAX_CACHE_SIZE { + if !self.map.contains_key(&key) { + if self.map.len() >= MAX_CACHE_SIZE { if let Some(oldest_key) = self.order.pop_front() { self.map.remove(&oldest_key); } + } + self.order.push_back(key); + self.map.insert(key, value); + } else { + // Update existing key's position in order queue + self.order.retain(|&k| k != key); + self.order.push_back(key); + self.map.insert(key, value); } - self.order.retain(|&k| k != key); - self.order.push_back(key); - self.map.insert(key, value); }The current implementation always updates the order and map, even if the key already exists, and it only checks cache size for new keys. The refactored version handles updates more efficiently.
255-255: Remove unnecessary comment) -> Result<String, ServiceError> { - // Salt size and nonce size // If password is not provided, return data as it isThis comment doesn't provide any useful information and appears to be a leftover. It should be removed for clarity.
17-19: Global cache implementation might benefit from additional considerationsThe global cache implementation is a good optimization but has some potential issues:
- It might grow unbounded if many different password/salt combinations are used
- No eviction policy for sensitive keys that are no longer needed
- Potential contention with RwLock if many threads are using it simultaneously
Consider:
- Adding a configuration option to disable the cache completely for high-security environments
- Implementing a periodic cleanup mechanism
- Using a more sophisticated concurrency primitive if high contention is expected
// Example for a configurable cache: static KEY_CACHE: LazyLock<Option<RwLock<SecretBox<SimpleCache>>>> = LazyLock::new(|| { if std::env::var("DISABLE_KEY_CACHE").is_ok() { None } else { Some(RwLock::new(SecretBox::new(Box::new(SimpleCache::new())))) } });
68-77: Typo in comment and optimization opportunity in Zeroize implementation-// Implemetation of zeroize required by secretbox +// Implementation of zeroize required by secretbox impl Zeroize for SimpleCache { fn zeroize(&mut self) { for value in self.map.values_mut() { value.zeroize(); } self.map.clear(); self.order.clear(); } }There's a typo in the comment ("Implemetation" should be "Implementation"). Additionally, consider zeroizing the keys as well, as they contain sensitive password-derived data.
159-204: Improve error handling in decrypt methodThe decrypt method has several places where errors are swallowed with generic messages:
fn decrypt(data: Vec<u8>, password: &str) -> Result<Vec<u8>, ServiceError> { // Split the encrypted data into nonce and data let (nonce, data) = data.split_at(NONCE_SIZE); let nonce: [u8; NONCE_SIZE] = nonce .try_into() - .map_err(|_| ServiceError::DecryptionError("Error converting nonce".to_string()))?; + .map_err(|e| ServiceError::DecryptionError(format!("Error converting nonce: {}", e)))?; let (salt, ciphertext) = data.split_at(SALT_SIZE); // Enecode salt from base64 to bytes let salt = SaltString::encode_b64(salt) - .map_err(|_| ServiceError::DecryptionError("Error decoding salt".to_string()))?; + .map_err(|e| ServiceError::DecryptionError(format!("Error decoding salt: {}", e)))?;Including the actual error in the message would make debugging much easier. This pattern should be applied throughout the method.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/crypto.rs(1 hunks)src/lib.rs(1 hunks)src/order.rs(3 hunks)src/prelude.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/lib.rs
- src/prelude.rs
- src/order.rs
🔇 Additional comments (2)
src/crypto.rs (2)
148-151: Improve buffer allocation efficiencyThe current code allocates a buffer with
with_capacitybut could be more precise:// Combine nonce and ciphertext - let mut encrypted = Vec::with_capacity(NONCE_SIZE + SALT_SIZE + ciphertext.len()); + let mut encrypted = Vec::with_capacity(NONCE_SIZE + SALT_SIZE + ciphertext.len()); encrypted.extend_from_slice(&nonce); encrypted.extend_from_slice(salt); encrypted.extend_from_slice(&ciphertext);This is actually correct as written. The buffer is allocated with exactly the capacity needed for nonce, salt, and ciphertext, which is good practice.
172-177: 🛠️ Refactor suggestionPotential issue with salt encoding for cache key generation
The way the salt is used for cache key generation might be problematic:
// Enecode salt from base64 to bytes let salt = SaltString::encode_b64(salt) .map_err(|_| ServiceError::DecryptionError("Error decoding salt".to_string()))?; // get hash value from salt and password - let cache_key = make_cache_key(password, salt.as_str().as_bytes()); + let cache_key = make_cache_key(password, salt.as_salt().as_ref());Using
salt.as_str().as_bytes()gets the B64-encoded representation of the salt, not the raw bytes. This could cause cache misses between encryption and decryption operations. Usingas_salt().as_ref()would access the raw salt bytes directly.Likely an incorrect or invalid review comment.
@grunch @Catrya
this is the related pull request of core to use columns encryption.
Summary by CodeRabbit
New Features
Improvements
Refactor
Removals