-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Encryption and key management (ready for review) #156
Conversation
WalkthroughThe pull request introduces significant changes across multiple files in the Changes
Assessment against linked issues
Possibly related PRs
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Outside diff range and nitpick comments (6)
packages/ciphernode/keyshare/Cargo.toml (1)
8-8
: Good choice of cryptographic libraries.The selection of cryptographic libraries aligns well with the PR objectives:
aes-gcm
: Provides authenticated encryptionargon2
: Strong password-based key derivationrand
: Secure random number generationzeroize
: Secure memory wipingConsider implementing the following security best practices:
- Use
zeroize
on all sensitive buffers containing keys or plaintext- Ensure Argon2 parameters are tuned appropriately for your use case
- Never reuse AES-GCM nonces with the same key
Also applies to: 10-10, 15-15, 18-18
packages/ciphernode/enclave/Cargo.toml (1)
16-16
: Consider using workspace versioning forconfig
dependency.The
config
crate is explicitly pinned to version 0.14.0 while other dependencies use workspace-level version management. This inconsistency could lead to maintenance overhead and version conflicts.Apply this change to align with the project's versioning strategy:
-config = "0.14.0" +config = { workspace = true }packages/ciphernode/data/src/snapshot.rs (2)
Line range hint
1-50
: Consider documenting encryption implications in trait documentation.Given that this PR introduces encryption features, it would be helpful to document how these traits interact with encrypted data, particularly:
- How
Snapshot
handles encrypted fields- Whether
Checkpoint
performs any encryption/decryption during persistence- How
FromSnapshot
andFromSnapshotWithParams
handle encrypted data during reconstitutionExample documentation addition for Snapshot trait:
/// This trait enables the self type to report their state snapshot +/// +/// When implementing this trait for types containing encrypted data: +/// - Ensure the Snapshot type can properly serialize encrypted fields +/// - Consider whether the snapshot should store encrypted or decrypted form pub trait Snapshot where Self: Sized, {
Line range hint
18-29
: Consider encryption-aware Repository implementation.Since this PR introduces encryption features, ensure that the
Repository
implementation properly handles encrypted data persistence. Consider implementing encryption/decryption at the Repository level rather than requiring each implementation to handle it separately.Key considerations:
- Implement transparent encryption/decryption in Repository operations
- Handle encryption key management within Repository
- Ensure atomic operations when writing encrypted data
packages/ciphernode/keyshare/src/keyshare.rs (1)
Line range hint
18-23
: Enhance memory safety for sensitive data storageThe
secret
field stores sensitive cryptographic material but isn't protected against memory dumps. Consider usingZeroizing<Vec<u8>>
to ensure secure cleanup of sensitive data when it goes out of scope.Apply this change:
pub struct Keyshare { fhe: Arc<Fhe>, store: Repository<KeyshareState>, bus: Addr<EventBus>, - secret: Option<Vec<u8>>, + secret: Option<Zeroizing<Vec<u8>>>, address: String, }tests/basic_integration/test.sh (1)
83-85
: Document and validate the secret parameter.The function should validate that the secret parameter is not empty and document its purpose.
Apply this diff to improve the implementation:
launch_ciphernode() { + # $1: Ethereum address of the ciphernode + # $2: Secret key used for encrypting Keyshare Secrets local address="$1" local secret="$2" + + if [ -z "$secret" ]; then + echo "Error: CIPHERNODE_SECRET must not be empty" >&2 + return 1 + fi + heading "Launch ciphernode $address"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
packages/ciphernode/Cargo.lock
is excluded by!**/*.lock
📒 Files selected for processing (10)
- packages/ciphernode/Cargo.toml (4 hunks)
- packages/ciphernode/data/src/snapshot.rs (1 hunks)
- packages/ciphernode/enclave/Cargo.toml (1 hunks)
- packages/ciphernode/enclave/src/main.rs (2 hunks)
- packages/ciphernode/keyshare/Cargo.toml (1 hunks)
- packages/ciphernode/keyshare/src/encryption.rs (1 hunks)
- packages/ciphernode/keyshare/src/keyshare.rs (5 hunks)
- packages/ciphernode/keyshare/src/lib.rs (1 hunks)
- packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs (3 hunks)
- tests/basic_integration/test.sh (4 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/ciphernode/keyshare/src/lib.rs
🧰 Additional context used
📓 Learnings (2)
packages/ciphernode/Cargo.toml (2)
Learnt from: ryardley PR: gnosisguild/enclave#133 File: packages/ciphernode/Cargo.toml:19-63 Timestamp: 2024-10-08T19:45:18.209Z Learning: The dependency versions specified are acceptable as per project standards.
Learnt from: ryardley PR: gnosisguild/enclave#133 File: packages/ciphernode/Cargo.toml:19-63 Timestamp: 2024-10-04T00:33:53.250Z Learning: The dependency versions specified are acceptable as per project standards.
packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs (1)
Learnt from: ryardley PR: gnosisguild/enclave#145 File: packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs:82-83 Timestamp: 2024-10-22T02:10:34.864Z Learning: In the file `packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs`, when reviewing test functions like `generate_pk_share`, minor performance optimizations (e.g., minimizing mutex locks) are not a priority.
🔇 Additional comments (13)
packages/ciphernode/enclave/Cargo.toml (2)
14-14
: LGTM: Good choice for error handling.The addition of
anyhow
is appropriate for handling various error types that may occur during cryptographic operations.
19-20
: Verify logging configuration for sensitive data.Adding tracing capabilities in a cryptographic context requires careful consideration to prevent accidental logging of sensitive information like encryption keys or plaintext data.
Consider:
- Implementing appropriate log filtering for sensitive data
- Using debug-level logging for crypto operations
- Adding log sanitization middleware
Run this check to identify potential sensitive data logging:
✅ Verification successful
Let me gather more context about the logging patterns, particularly focusing on the encryption and cryptographic operations.
The above scripts will help us understand:
- The context around encryption operations and their logging
- Any trace-level logging specifically in crypto-related modules
- Logging configuration files that might contain filtering rules
Let's check the logger implementation and configuration since we found the logger module, and also look for any encryption-related logging patterns.
This will help us understand:
- The logging implementation and any built-in safeguards
- The context of encryption-related logging
- The patterns of sensitive data handling in encryption methods
Based on the gathered information, I can now provide the final response:
Logging implementation appears secure with appropriate safeguards
The logging implementation in the codebase shows good security practices:
- The SimpleLogger only logs event metadata and error conditions without exposing sensitive data
- No direct logging of encryption keys, secrets, or encrypted content was found
- Error logging is properly abstracted through the EnclaveEvent system
- The only encryption-related log found is a high-level error message that doesn't expose sensitive data
The tracing dependencies can be safely used as currently implemented, as the logging patterns show appropriate abstraction and sanitization of sensitive information.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for potential logging of sensitive data rg -i "trace!|debug!|info!|warn!|error!" | grep -i "key\|secret\|password\|encrypt"Length of output: 722
Script:
#!/bin/bash # Search for encryption-related functions and their logging ast-grep --pattern 'fn $_($$$) { $$$ encrypt$_($$$) $$$ }' # Also check for any trace-level logging in crypto-related files rg -i "trace!" packages/ciphernode/crypto/ rg -i "trace!" packages/ciphernode/enclave/ # Look for logging configuration files fd -e rs -e toml . packages/ciphernode/ --exec grep -l -i "log\|trace"Length of output: 1137
Script:
#!/bin/bash # Check logger implementation for any filtering or sanitization cat packages/ciphernode/logger/src/lib.rs cat packages/ciphernode/logger/src/logger.rs # Look for encryption-related logging in the codebase rg -i "encrypt" -C 2 packages/ciphernode/ | grep -i "trace\|debug\|info\|warn\|error" # Check for any sensitive data handling patterns ast-grep --pattern 'impl $_ { $$$ fn encrypt($$$) { $$$ } $$$ }'Length of output: 1810
packages/ciphernode/Cargo.toml (2)
22-22
: LGTM! Cryptographic dependencies are appropriate and up-to-date.The selected cryptographic dependencies (
aes-gcm
,argon2
, andzeroize
) are well-maintained, widely-used implementations that align perfectly with the PR's objective of implementing secure AES encryption and key management.Also applies to: 31-31, 68-68
45-45
: LGTM! Property-based testing is valuable for cryptographic implementations.Adding
proptest
is a good choice as it will help ensure the robustness of the encryption implementation through property-based testing.packages/ciphernode/data/src/snapshot.rs (1)
1-4
: Verify the removal of DataStore import.While removing unused imports is good practice, let's verify that the
DataStore
removal doesn't impact the new encryption features being introduced.packages/ciphernode/enclave/src/main.rs (1)
1-2
: Verify environment variable handling.While the
std::env
import is added, there's no validation of the requiredCIPHERNODE_SECRET
environment variable during startup. This could lead to runtime errors if the secret is missing.Let's verify how
CIPHERNODE_SECRET
is used in the codebase:Consider adding validation during startup:
// Suggested addition after line 43 let secret = std::env::var("CIPHERNODE_SECRET") .expect("CIPHERNODE_SECRET environment variable must be set");packages/ciphernode/keyshare/src/keyshare.rs (1)
1-1
: Verify encryption dependencies configurationLet's ensure the encryption-related dependencies are properly configured with secure versions and features.
tests/basic_integration/test.sh (1)
121-124
: 🛠️ Refactor suggestionConsider testing with different secrets per node.
Currently, all nodes use the same secret. To thoroughly test encryption isolation and key management, consider using different secrets for each node.
Let's verify if the system requires the same secret across all nodes:
If different secrets are supported, consider this improvement:
-launch_ciphernode "$CIPHERNODE_ADDRESS_1" "$CIPHERNODE_SECRET" -launch_ciphernode "$CIPHERNODE_ADDRESS_2" "$CIPHERNODE_SECRET" -launch_ciphernode "$CIPHERNODE_ADDRESS_3" "$CIPHERNODE_SECRET" -launch_ciphernode "$CIPHERNODE_ADDRESS_4" "$CIPHERNODE_SECRET" +# Test with different secrets to ensure proper encryption isolation +launch_ciphernode "$CIPHERNODE_ADDRESS_1" "test-secret-node1" +launch_ciphernode "$CIPHERNODE_ADDRESS_2" "test-secret-node2" +launch_ciphernode "$CIPHERNODE_ADDRESS_3" "test-secret-node3" +launch_ciphernode "$CIPHERNODE_ADDRESS_4" "test-secret-node4"✅ Verification successful
Let me gather more information about the secret handling in the codebase.
The previous results showed some references to secret shares in the keyshare module, but we need more context about how secrets are validated and used in the ciphernode implementation to determine if different secrets per node are supported or if there are any constraints requiring shared secrets.
Let me check one more aspect about how the secrets are used in the encryption/decryption process.
The keyshare implementation shows that each node generates its own keyshare and secret independently during the
CiphernodeSelected
event, but we need to verify if the FHE (Fully Homomorphic Encryption) implementation requires shared secrets for proper communication between nodes.
Based on the code analysis, I can now provide the final response:
Using different secrets per node is supported and recommended
The codebase reveals that:
- Each node independently generates its own secret key and public key share via
generate_keyshare()
- The FHE implementation uses a threshold cryptography scheme where:
- Nodes generate individual key shares
- Public keys are aggregated (
get_aggregate_public_key
)- Decryption shares are combined (
get_aggregate_plaintext
)- There's no requirement or validation enforcing shared secrets between nodes
The original review suggestion to use different secrets per node is correct and aligns with the system's design for proper encryption isolation testing.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for any documentation or code that indicates whether nodes should share secrets rg -i "secret.*share" --type rust rg -i "same.*secret" --type rustLength of output: 516
Script:
#!/bin/bash # Check how secrets are used in the ciphernode implementation ast-grep --pattern 'fn launch_ciphernode' # Look for secret validation or requirements rg -i "secret.*valid" --type rust rg -i "secret.*require" --type rust # Check keyshare implementation for any constraints fd keyshare.rs --exec cat {}Length of output: 5561
Script:
#!/bin/bash # Check how secrets are used in encryption/decryption and FHE operations fd fhe.rs --exec cat {} # Look for any communication between nodes that might require shared secrets rg -i "node.*communicate" --type rust rg -i "node.*connection" --type rustLength of output: 5656
packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs (1)
274-274
: Verify secret handling in remaining test casesThe secret environment variable is only set in two test cases. Other test cases might need the same environment setup for consistency.
Let's verify if other test cases require the secret:
Also applies to: 380-380
packages/ciphernode/keyshare/src/encryption.rs (4)
12-23
: Ensure Argon2 and AES parameters meet current security recommendationsThe Argon2 parameters (
ARGON2_M_COST
,ARGON2_T_COST
,ARGON2_P_COST
) and AES parameters (AES_SALT_LEN
,AES_NONCE_LEN
) are critical for security and performance. Please verify that these values align with the latest security best practices to prevent vulnerabilities. For example, increasing the memory cost or iterations may enhance resistance against brute-force attacks.
24-41
:argon2_derive_key
function is implemented correctly with secure practicesThe key derivation function correctly uses Argon2id with appropriate parameters and zeroizes sensitive data. Error handling is properly managed.
43-78
:encrypt_data
function securely encrypts data with proper handling of sensitive informationThe
encrypt_data
function appropriately handles encryption using AES-GCM and derives the key securely using Argon2. Sensitive data is zeroized after use, and random salt and nonce are correctly generated and stored.
80-105
:decrypt_data
function correctly decrypts data with proper error handlingThe
decrypt_data
function properly extracts the salt and nonce, derives the key, and decrypts the ciphertext. Error handling is managed correctly, and sensitive data is zeroized.
packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- packages/ciphernode/keyshare/Cargo.toml (1 hunks)
- packages/ciphernode/keyshare/src/keyshare.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ciphernode/keyshare/Cargo.toml
🧰 Additional context used
🔇 Additional comments (4)
packages/ciphernode/keyshare/src/keyshare.rs (4)
Line range hint
1-16
: LGTM! Appropriate security-focused imports.The addition of
zeroize
and encryption-related imports aligns well with the security objectives.
128-134
: LGTM! Proper encryption integrationThe integration with the new encryption system is implemented correctly with appropriate error handling.
52-56
: 🛠️ Refactor suggestionEnhance memory safety with Zeroizing wrapper
While the encryption is implemented correctly, consider wrapping the input data with
Zeroizing
to ensure secure handling:- fn set_secret(&mut self, mut data: Vec<u8>) -> Result<()> { + fn set_secret(&mut self, data: Zeroizing<Vec<u8>>) -> Result<()> { - let encrypted = encrypt_data(&mut data)?; + let encrypted = encrypt_data(&data)?; self.secret = Some(encrypted); Ok(()) }Likely invalid or redundant comment.
161-171
: 🛠️ Refactor suggestionVerify secure cleanup of decrypted secret
While the decryption process is correct, ensure the secret is securely cleared from memory after use:
Consider wrapping the secret in a scope to ensure cleanup:
let Ok(secret) = self.get_secret() else { self.bus.err( EnclaveErrorType::Decryption, anyhow!("Secret not available for Keyshare for e3_id {e3_id}"), ); return; }; - - let Ok(decryption_share) = self.fhe.decrypt_ciphertext(DecryptCiphertext { - ciphertext: ciphertext_output.clone(), - unsafe_secret: secret, - }) else { + let decryption_share = { + let Ok(result) = self.fhe.decrypt_ciphertext(DecryptCiphertext { + ciphertext: ciphertext_output.clone(), + unsafe_secret: secret, + }) else { + self.bus.err( + EnclaveErrorType::Decryption, + anyhow!("error decrypting ciphertext: {:?}", ciphertext_output), + ); + return; + }; + result + }; // secret is dropped here
Just going to refactor to have a struct accept a secret so I can run tests in parallel as more comprehensive tests fail. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Outside diff range and nitpick comments (1)
packages/ciphernode/keyshare/src/keyshare.rs (1)
51-69
: Document encryption implementation and future improvementsThe current implementation aligns with Phase 1 of the security enhancement plan, using environment variables for encryption. Consider adding documentation that:
- Describes the current encryption strategy
- Outlines plans for Phase 2 key rotation
- Lists security considerations for production deployment
This will help future maintainers understand the security roadmap and implementation decisions.
Add the following documentation above the
set_secret
method:/// Encrypts and stores the secret using AES-GCM with a key derived from CIPHERNODE_SECRET. /// /// # Security Considerations /// - Current implementation (Phase 1): /// - Uses environment variable for key material /// - No key rotation support /// /// # Future Improvements (Phase 2) /// - Implement key rotation /// - Store encrypted keys separately /// - Add key version tracking
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- packages/ciphernode/keyshare/src/encryption.rs (1 hunks)
- packages/ciphernode/keyshare/src/keyshare.rs (5 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/ciphernode/keyshare/src/encryption.rs (1)
Learnt from: ryardley PR: gnosisguild/enclave#156 File: packages/ciphernode/keyshare/src/keyshare.rs:52-56 Timestamp: 2024-10-23T01:59:19.277Z Learning: The `encrypt_data` function zeroizes the plaintext data internally, so additional zeroizing is unnecessary.
🔇 Additional comments (3)
packages/ciphernode/keyshare/src/keyshare.rs (2)
60-69
:⚠️ Potential issueValidate environment variable presence during initialization
The current implementation attempts to create an
Encryptor
for each operation, which could fail if the environment variable is not set. This should be validated during initialization instead.#!/bin/bash # Check if CIPHERNODE_SECRET is validated elsewhere rg "CIPHERNODE_SECRET" --type rust
1-1
: Verify usage of imported ZeroizingThe
Zeroizing
import appears to be unused in the current implementation. If it's not being used elsewhere in the codebase, consider removing it.Also applies to: 12-15
packages/ciphernode/keyshare/src/encryption.rs (1)
24-41
: LGTM! Secure implementation of key derivationThe implementation follows cryptographic best practices with proper:
- Memory safety through zeroization
- Error handling
- Parameter validation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (2)
packages/ciphernode/keyshare/src/encryption.rs (2)
61-62
: Consider using a generic error message for encryption failuresThe specific error message "Could not AES Encrypt given plaintext" could potentially leak timing information in case of errors. Consider using a generic message like "Encryption failed" instead.
- .map_err(|_| anyhow!("Could not AES Encrypt given plaintext."))?; + .map_err(|_| anyhow!("Encryption failed"))?;
102-105
: Add documentation for the Encryptor structConsider adding documentation comments to explain the purpose, usage, and security considerations of the Encryptor struct.
+/// Encryptor provides secure encryption and decryption of data using AES-GCM. +/// It implements ZeroizeOnDrop to ensure sensitive key material is securely erased from memory. +/// +/// # Example +/// ``` +/// use ciphernode::Encryptor; +/// +/// let encryptor = Encryptor::new("my_secure_password".to_owned()); +/// let mut data = b"secret data".to_vec(); +/// let encrypted = encryptor.encrypt_data(&mut data).unwrap(); +/// let decrypted = encryptor.decrypt_data(&encrypted).unwrap(); +/// ``` #[derive(ZeroizeOnDrop)] pub struct Encryptor { key: Zeroizing<Vec<u8>>, }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- packages/ciphernode/keyshare/src/encryption.rs (1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/ciphernode/keyshare/src/encryption.rs (1)
Learnt from: ryardley PR: gnosisguild/enclave#156 File: packages/ciphernode/keyshare/src/encryption.rs:77-100 Timestamp: 2024-10-23T02:49:42.460Z Learning: In the `packages/ciphernode/keyshare/src/encryption.rs` file, avoid adding extra complexity to the `encrypt_data` and `decrypt_data` functions, such as adding a magic number or version prefix to the encrypted data format, unless there is a significant benefit.
🔇 Additional comments (2)
packages/ciphernode/keyshare/src/encryption.rs (2)
77-100
: LGTM! Well-structured decryption implementation.The decryption function includes proper input validation, clear separation of components, and secure error handling.
133-264
: Excellent test coverage!The test suite is comprehensive, covering:
- Basic encryption/decryption
- Empty and large data handling
- Password mismatches
- Binary and Unicode data
- Corrupted data
- Multiple encryption cycles
- Performance measurements
To complete this we need to build these commands:
We can also look at this if it is easy:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- packages/ciphernode/cipher/src/cipher.rs (1 hunks)
- packages/ciphernode/cipher/src/password_manager.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ciphernode/cipher/src/cipher.rs
🔇 Additional comments (9)
packages/ciphernode/cipher/src/password_manager.rs (9)
12-17
: TraitPasswordManager
is well-definedThe trait
PasswordManager
is clearly defined with asynchronous methods for password management, providing a consistent interface for different implementations.
19-29
:InMemPasswordManager
implementation is appropriateThe
InMemPasswordManager
correctly handles sensitive data in memory usingZeroizing
, ensuring that the data is securely wiped when no longer needed.
40-57
:EnvPasswordManager
correctly implementsPasswordManager
traitThe implementation properly retrieves, sets, and deletes the key, handling sensitive data securely with
Zeroizing
.
59-76
:InMemPasswordManager
correctly implementsPasswordManager
traitThe in-memory password manager functions as expected, securely managing sensitive data and conforming to the defined trait.
78-88
:FilePasswordManager
constructor is correctly implementedThe constructor for
FilePasswordManager
properly initializes the path to the keyfile.
90-148
: Implementation ofPasswordManager
forFilePasswordManager
is well-structuredThe methods for managing the keyfile are logically organized and make appropriate use of asynchronous execution.
98-106
: Note on existing potential issue with file permissionsPrevious comments have addressed potential race conditions and security concerns with file permission enforcement in
get_key
. Ensure that these are resolved appropriately.
117-147
: Note on existing potential issue with keyfile creation permissionsPrevious comments have highlighted concerns regarding the window of broader file permissions during keyfile creation in
set_key
. Verify that these issues have been addressed.
150-164
: Note on existing suggestion to simplifyensure_file_permissions
There is an existing suggestion to simplify the
ensure_file_permissions
function for improved clarity and reliability. Please consider this in your revisions.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Outside diff range and nitpick comments (4)
packages/ciphernode/enclave/src/main.rs (2)
Line range hint
8-21
: Consider moving ASCII art to a separate constants file.The ASCII art constant could be moved to a separate constants module to keep the main file focused on core functionality and improve maintainability.
22-25
: Improve function name and newline handling.The function could be improved by:
- Using a more descriptive name (e.g.,
print_banner
ordisplay_logo
)- Consolidating multiple newline prints
-pub fn owo() { +pub fn print_banner() { println!("\n\n\n\n\n{}", OWO); - println!("\n\n\n\n"); + println!("\n"); }packages/ciphernode/keyshare/src/keyshare.rs (1)
166-176
: Consider adding debug logging for troubleshootingWhile the error handling is solid, consider adding debug-level logging before encryption/decryption operations to aid in troubleshooting issues in production. Ensure logs don't contain sensitive information.
Example:
+ tracing::debug!("Attempting to decrypt ciphertext for e3_id: {}", e3_id); let Ok(secret) = self.get_secret() else { self.bus.err( EnclaveErrorType::Decryption, anyhow!("Secret not available for Keyshare for e3_id {e3_id}"), ); return; };
packages/ciphernode/config/src/app_config.rs (1)
213-318
: Add tests for error casesThe test suite lacks coverage for error cases such as invalid configurations and directory creation failures.
Add the following test cases:
- Invalid Ethereum addresses in configuration
- Missing required fields
- Directory creation failures
- Invalid YAML syntax
Example test:
#[test] fn test_invalid_config() { Jail::expect_with(|jail| { let home = format!("{}", jail.directory().to_string_lossy()); jail.set_env("HOME", &home); jail.create_file( "config.yaml", r#" chains: - name: "hardhat" rpc_url: "" # Invalid: empty RPC URL contracts: enclave: "invalid-address" # Invalid Ethereum address "#, )?; let result = load_config(Some("config.yaml")); assert!(result.is_err()); Ok(()) }); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
packages/ciphernode/Cargo.lock
is excluded by!**/*.lock
📒 Files selected for processing (6)
- packages/ciphernode/config/src/app_config.rs (1 hunks)
- packages/ciphernode/enclave/src/main.rs (2 hunks)
- packages/ciphernode/evm/Cargo.toml (1 hunks)
- packages/ciphernode/evm/src/helpers.rs (2 hunks)
- packages/ciphernode/keyshare/src/keyshare.rs (7 hunks)
- tests/basic_integration/lib/clean_folders.sh (1 hunks)
🧰 Additional context used
📓 Learnings (1)
tests/basic_integration/lib/clean_folders.sh (1)
Learnt from: ryardley PR: gnosisguild/enclave#156 File: tests/basic_integration/lib/clean_folders.sh:1-3 Timestamp: 2024-10-28T11:35:41.176Z Learning: In the file `tests/basic_integration/lib/clean_folders.sh`, the `clean_folders` function does not require argument validation for `SCRIPT_DIR` as per the user's preference.
🪛 Shellcheck
tests/basic_integration/lib/clean_folders.sh
[warning] 3-3: In POSIX sh, 'local' is undefined.
(SC3043)
🔇 Additional comments (14)
packages/ciphernode/evm/Cargo.toml (1)
12-18
: LGTM! Dependencies align well with security requirements.The added dependencies support the PR's encryption and key management objectives:
cipher
: For implementing AES encryptiondata
: For managing encrypted data persistencezeroize
: For secure handling of sensitive data in memoryLet's verify the workspace configuration:
✅ Verification successful
Dependencies are correctly configured in the workspace
The verification confirms that:
- All dependencies are properly configured in the workspace root (
packages/ciphernode/Cargo.toml
)zeroize
is correctly specified in the workspace dependencies with version "1.6.0"- The dependency is consistently used across packages with workspace inheritance
- Local dependencies (
data
,cipher
,sortition
) are correctly referenced using relative paths🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify workspace configuration and dependency versions # Test 1: Check if these are workspace dependencies rg -A 10 '^\[workspace\]' Cargo.toml # Test 2: Verify consistent versions across workspace rg --type toml 'zeroize.*workspace.*true'Length of output: 358
Script:
#!/bin/bash # Let's check for the workspace configuration in the root Cargo.toml fd -t f "^Cargo.toml$" -d 1 --exec cat {} # Also check if there's a workspace configuration in the packages directory fd -t f "^Cargo.toml$" -d 1 packages/ --exec cat {}Length of output: 101
Script:
#!/bin/bash # Let's try a different approach to locate and examine Cargo.toml files # First, find all Cargo.toml files in the repository fd -t f "Cargo.toml" # Then examine the directory structure to understand the workspace layout ls -R packages/ciphernode/Length of output: 3947
Script:
#!/bin/bash # Let's check the root workspace configuration cat packages/ciphernode/Cargo.toml # And verify the version specifications for zeroize in cipher package cat packages/ciphernode/cipher/Cargo.toml | grep -A 5 -B 5 "zeroize"Length of output: 2066
tests/basic_integration/lib/clean_folders.sh (3)
2-3
: LGTM!The function definition is clean and straightforward.
🧰 Tools
🪛 Shellcheck
[warning] 3-3: In POSIX sh, 'local' is undefined.
(SC3043)
5-6
: 🛠️ Refactor suggestionImprove output artifacts cleanup reliability.
The current cleanup might fail silently if no files match or if the directory doesn't exist.
# Delete output artifacts - rm -rf "$SCRIPT_DIR/output/"* + if [ -d "$SCRIPT_DIR/output" ]; then + rm -rf "$SCRIPT_DIR/output/"* || echo "Warning: Failed to clean output directory" >&2 + fiLikely invalid or redundant comment.
8-12
: 🛠️ Refactor suggestionConsider improving maintainability of preserved files list.
The current implementation works well but could be more maintainable.
# Delete enclave artifacts + # Define preserved files for better maintainability + local preserved_files=".*/\(config\.yaml\|\.gitignore\)$" for name in cn1 cn2 cn3 cn4 ag; do # List all files and directories except config.yaml, then delete them - find "$SCRIPT_DIR/lib/$name" -mindepth 1 ! -regex '.*/config\.yaml$' ! -regex '.*/.gitignore$' -exec rm -rf {} + + find "$SCRIPT_DIR/lib/$name" -mindepth 1 ! -regex "$preserved_files" -exec rm -rf {} + doneLikely invalid or redundant comment.
packages/ciphernode/enclave/src/main.rs (4)
27-37
: Well-structured CLI implementation!The CLI structure effectively implements the required commands for key management while following best practices:
- Clear documentation
- Global config option
- Proper subcommand organization
57-57
: Update theactix_rt::main
attribute.As mentioned in the previous review, the
actix_rt
crate is deprecated. Update toactix::main
to ensure future compatibility.
Line range hint
1-69
: Consider secure memory handling for sensitive data.Given that this CLI handles sensitive data like private keys and secrets, consider:
- Using secure memory wiping after processing sensitive data
- Adding warnings if running with elevated privileges
- Implementing proper file permissions for config files
#!/bin/bash # Check for existing security measures in the codebase echo "Checking for secure memory handling..." rg -i "secure|wipe|zeroize" echo "Checking for permission handling..." rg -i "chmod|permission|umask"
39-53
: 🛠️ Refactor suggestionAdd config path validation.
While the implementation is clean, consider validating the config path before loading it to provide better error messages.
pub async fn execute(self) -> Result<()> { let config_path = self.config.as_deref(); + + if let Some(path) = config_path { + if !std::path::Path::new(path).exists() { + anyhow::bail!("Config file not found: {}", path); + } + } let config = load_config(config_path)?;packages/ciphernode/keyshare/src/keyshare.rs (3)
4-4
: LGTM: Proper integration of cipher functionalityThe addition of the
Cipher
type and its integration into the structs is well-implemented with proper thread-safety considerations usingArc
.Also applies to: 21-21, 33-33, 49-49, 99-99
53-70
: LGTM: Secure implementation of secret managementThe implementation follows security best practices:
- Proper encryption/decryption using the cipher
- Efficient memory handling with no unnecessary copies
- Clear error messages without leaking sensitive information
134-139
: LGTM: Robust error handling in event handlersThe error handling implementation properly propagates errors with appropriate error types while maintaining the security of sensitive data.
packages/ciphernode/evm/src/helpers.rs (2)
13-15
: LGTM: Imports are appropriate for secure key management.The added imports provide the necessary components for secure key handling and encryption operations.
Also applies to: 18-18, 21-21
143-157
:⚠️ Potential issueSecurity improvements needed for private key handling.
While the function correctly uses
Zeroizing
for secure memory handling, there are several security considerations to address:
- Add validation for the private key format before parsing
- Avoid unnecessary copying of sensitive data with
to_vec()
- Consider adding logging for security-relevant events
Apply these security improvements:
pub async fn get_signer_from_repository( repository: Repository<Vec<u8>>, cipher: &Arc<Cipher>, ) -> Result<Arc<PrivateKeySigner>> { let Some(private_key_encrypted) = repository.read().await? else { + trace!("Attempt to access non-existent private key"); bail!("No private key was found!") }; let encoded_private_key = Zeroizing::new(cipher.decrypt_data(&private_key_encrypted)?); - let private_key = Zeroizing::new(String::from_utf8(encoded_private_key.to_vec())?); + let private_key = Zeroizing::new(String::from_utf8(encoded_private_key.as_ref().to_vec())?); + + // Validate private key format + if !private_key.starts_with("0x") || private_key.len() != 66 { + trace!("Invalid private key format detected"); + bail!("Invalid private key format - must be 0x-prefixed and 32 bytes"); + } let signer = private_key.parse()?; + trace!("Successfully loaded private key from repository"); Ok(Arc::new(signer)) }Let's verify the private key format used elsewhere in the codebase:
packages/ciphernode/config/src/app_config.rs (1)
95-97
: Clarify the purpose of use_in_mem_storeThe method always returns
false
, which suggests it might be a placeholder or incomplete implementation.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (3)
packages/ciphernode/enclave/src/main.rs (1)
22-25
: Consider memory cleanup after ASCII art display.Since this is a security-focused application dealing with sensitive data, ensure that any large string constants are properly cleaned up from memory after use.
Apply this diff to implement secure string handling:
pub fn owo() { - println!("\n\n\n\n\n{}", OWO); - println!("\n\n\n\n"); + { + let output = format!("\n\n\n\n\n{}\n\n\n\n", OWO); + println!("{}", output); + // Ensure the formatted string is cleaned up + drop(output); + } }packages/ciphernode/cipher/src/password_manager.rs (2)
42-47
: Enhance error messages for better debuggingThe error message "No key found" could be more specific about the context.
Consider:
- Err(anyhow!("No key found")) + Err(anyhow!("No environment key found in EnvPasswordManager"))
154-168
: Add documentation for permission constantsThe function would benefit from documentation explaining the permission constants and their security implications.
Add documentation:
+/// Ensures file permissions are set correctly for secure key storage. +/// +/// # Arguments +/// * `path` - Path to the keyfile +/// * `perms` - Unix permission bits: +/// - 0o400: Read-only for owner (used for stored keys) +/// - 0o600: Read-write for owner (used during key operations) fn ensure_file_permissions(path: &PathBuf, perms: u32) -> Result<()> {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- packages/ciphernode/cipher/src/password_manager.rs (1 hunks)
- packages/ciphernode/enclave/src/commands/password/create.rs (1 hunks)
- packages/ciphernode/enclave/src/main.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ciphernode/enclave/src/commands/password/create.rs
🧰 Additional context used
📓 Learnings (1)
packages/ciphernode/cipher/src/password_manager.rs (3)
Learnt from: ryardley PR: gnosisguild/enclave#156 File: packages/ciphernode/cipher/src/password_manager.rs:126-131 Timestamp: 2024-10-28T10:50:28.550Z Learning: In `FilePasswordManager::set_key`, since we need to write to the keyfile, we must open it with write permissions (e.g., `0o600`). Setting permissions to `0o400` at creation time is not feasible because it would prevent writing to the file.
Learnt from: ryardley PR: gnosisguild/enclave#156 File: packages/ciphernode/cipher/src/password_manager.rs:34-37 Timestamp: 2024-10-28T10:46:32.167Z Learning: In `EnvPasswordManager::new`, wrapping the environment variable into `Zeroizing` upon retrieval is unnecessary because it is dropped immediately.
Learnt from: ryardley PR: gnosisguild/enclave#156 File: packages/ciphernode/cipher/src/password_manager.rs:121-123 Timestamp: 2024-10-28T10:49:45.743Z Learning: In `packages/ciphernode/cipher/src/password_manager.rs`, the `FilePasswordManager` struct includes an overwrite method to allow securely overwriting existing key files when necessary.
🔇 Additional comments (6)
packages/ciphernode/enclave/src/main.rs (3)
57-57
: Update theactix_rt::main
attribute.As noted in previous reviews,
actix_rt
is deprecated. Update toactix::main
for future compatibility.
39-53
: Review error messages for potential information leakage.When handling errors in a security-focused application, ensure that error messages don't expose sensitive information like keys, paths, or internal states.
Run the following script to analyze error handling in command modules:
#!/bin/bash # Search for error handling patterns in command modules rg "Err\(|Error::new|anyhow::" packages/ciphernode/enclave/src/commands/
31-33
: Verify config file permissions on load.Since the config file may contain sensitive information, ensure that proper file permissions are checked when loading the config file.
Run the following script to check if file permissions are validated:
packages/ciphernode/cipher/src/password_manager.rs (3)
1-17
: Well-structured trait definition with appropriate security considerationsThe trait definition and imports demonstrate good practices:
- Using Zeroizing for secure memory handling
- Async trait for non-blocking operations
- Comprehensive error handling with anyhow
61-64
: Consider avoiding clone() on sensitive dataWhile Zeroizing handles secure memory cleanup, cloning sensitive data creates additional copies in memory that need to be tracked and cleaned up.
Consider using references or implementing a more secure way to handle the key retrieval:
- if let Some(key) = self.0.clone() { + if let Some(ref key) = self.0 { - return Ok(key); + return Ok(key.clone()); }
117-151
: 🛠️ Refactor suggestionConsider checking parent directory permissions
While file permissions are properly handled, the security of the keyfile also depends on the parent directory permissions.
Add directory permission check:
async fn set_key(&mut self, contents: Zeroizing<Vec<u8>>) -> Result<()> { let path = &self.path; + + // Check parent directory permissions + if let Some(parent) = path.parent() { + let metadata = fs::metadata(parent) + .context("Failed to get parent directory metadata")?; + let dir_mode = metadata.permissions().mode() & 0o777; + if dir_mode & 0o077 != 0 { + bail!("Parent directory has too permissive permissions"); + } + }
Closes: #128
This PR addresses some security issues around key management whilst preparing for docker compatibility and as a consequence dappnode deployment.
config.yaml
instead of passing it in to CLI you can see some config.yaml examples setup for the test hereconfig.yaml
in~/.config/enclave/config.yaml
for managing config. See AppConfig~/.config/encalve/key
which can be overridden in theconfig.yaml
This is managed by the PasswordManager~/.local/enclave/db
by default according to linux conventionsYou can now use the cli in the repo with the command:
Be aware this will take time to compile if you have not yet done it so there might be a lag between when you attempt to run the command and the time the command responds.
This PR means that node operators can follow docker conventions for loading secrets from a protected position within the file system as opposed to using env vars for better security so a user can use kubernetes / docker swarm or something else to run their node should they desire without the danger of accidentally leaking env vars.
Also we have attempted to follow linux XDG conventions in terms of defaults for data location.
In terms of encryption we basically pad the password with Argon2 and then use it to encrypt the values. With one salt per encryption. We don't however store the padded secret but instead store the raw secret in the file in accordance with what a node operator would do as they will assume an encrypted channel to the volume mount and will likely simply set their secret through docker secrets or using a secrets manager which often loads secrets via encrypted mounted volumes.
We could if it is found to be a requirement - use individual random keys for storing the secrets that are encrypted by the password but for now this seems like overkill at this preliminary stage - so here we are just using the master password approach and can look at key rotation techniques later.
Summary by CodeRabbit
New Features
cipher
andconfig
.GetErrors
message type to enhance error management in the EventBus.setup_aggregator
andsetup_ciphernode
functions for improved modularity and asynchronous operations.Bug Fixes
Documentation
Chores