summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2025-09-29 16:01:42 +0800
committerGitHub <noreply@github.com>2025-09-29 16:01:42 +0800
commit50b801a11e1c3bb3012ac189197b7e84663ba902 (patch)
treeaa9f86e49e01189a4350816c782ee38ccacd8dfd
parent114ed3c65d9d5765a9e9553f1d2f14a3772c6867 (diff)
parentd6283f0964afcf093e4c53df3c05ac9af8e28596 (diff)
Merge pull request #14 from JustEnoughVCS/jvcs_dev
Jvcs dev
-rw-r--r--Cargo.lock32
-rw-r--r--crates/utils/tcp_connection/Cargo.toml1
-rw-r--r--crates/utils/tcp_connection/src/behaviour.rs1
-rw-r--r--crates/utils/tcp_connection/src/instance.rs285
-rw-r--r--crates/utils/tcp_connection/src/instance_challenge.rs297
-rw-r--r--crates/utils/tcp_connection/src/lib.rs2
-rw-r--r--crates/vcs/src/data/sheet.rs163
-rw-r--r--crates/vcs/src/data/vault/virtual_file.rs4
-rw-r--r--crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs39
-rw-r--r--src/lib.rs14
10 files changed, 507 insertions, 331 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dcce341..2d5e176 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -29,6 +29,18 @@ dependencies = [
]
[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -82,6 +94,19 @@ dependencies = [
]
[[package]]
+name = "blake3"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -210,6 +235,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1148,6 +1179,7 @@ name = "tcp_connection"
version = "0.1.0"
dependencies = [
"base64",
+ "blake3",
"crc",
"ed25519-dalek",
"pem",
diff --git a/crates/utils/tcp_connection/Cargo.toml b/crates/utils/tcp_connection/Cargo.toml
index 22466c8..8d62a26 100644
--- a/crates/utils/tcp_connection/Cargo.toml
+++ b/crates/utils/tcp_connection/Cargo.toml
@@ -25,3 +25,4 @@ rand = "0.10.0-rc.0"
base64 = "0.22.1"
pem = "3.0.5"
crc = "3.3.0"
+blake3 = "1.5.0"
diff --git a/crates/utils/tcp_connection/src/behaviour.rs b/crates/utils/tcp_connection/src/behaviour.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/crates/utils/tcp_connection/src/behaviour.rs
@@ -0,0 +1 @@
+
diff --git a/crates/utils/tcp_connection/src/instance.rs b/crates/utils/tcp_connection/src/instance.rs
index fd620e2..dc49591 100644
--- a/crates/utils/tcp_connection/src/instance.rs
+++ b/crates/utils/tcp_connection/src/instance.rs
@@ -1,11 +1,5 @@
use std::{path::Path, time::Duration};
-use rand::TryRngCore;
-use rsa::{
- RsaPrivateKey, RsaPublicKey,
- pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey},
- sha2,
-};
use serde::Serialize;
use tokio::{
fs::{File, OpenOptions},
@@ -13,12 +7,7 @@ use tokio::{
net::TcpStream,
};
-use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
-use ring::rand::SystemRandom;
-use ring::signature::{
- self, ECDSA_P256_SHA256_ASN1, ECDSA_P384_SHA384_ASN1, EcdsaKeyPair, RSA_PKCS1_2048_8192_SHA256,
- UnparsedPublicKey,
-};
+use ring::signature::{self};
use crate::error::TcpTargetError;
@@ -525,276 +514,4 @@ impl ConnectionInstance {
Ok(())
}
-
- /// Initiates a challenge to the target machine to verify connection security
- ///
- /// This method performs a cryptographic challenge-response authentication:
- /// 1. Generates a random 32-byte challenge
- /// 2. Sends the challenge to the target machine
- /// 3. Receives a digital signature of the challenge
- /// 4. Verifies the signature using the appropriate public key
- ///
- /// # Arguments
- /// * `public_key_dir` - Directory containing public key files for verification
- ///
- /// # Returns
- /// * `Ok(true)` - Challenge verification successful
- /// * `Ok(false)` - Challenge verification failed
- /// * `Err(TcpTargetError)` - Error during challenge process
- pub async fn challenge(
- &mut self,
- public_key_dir: impl AsRef<Path>,
- ) -> Result<bool, TcpTargetError> {
- // Generate random challenge
- let mut challenge = [0u8; 32];
- rand::rngs::OsRng
- .try_fill_bytes(&mut challenge)
- .map_err(|e| {
- TcpTargetError::Crypto(format!("Failed to generate random challenge: {}", e))
- })?;
-
- // Send challenge to target
- self.stream.write_all(&challenge).await?;
- self.stream.flush().await?;
-
- // Read signature from target
- let mut signature = Vec::new();
- let mut signature_len_buf = [0u8; 4];
- self.stream.read_exact(&mut signature_len_buf).await?;
-
- let signature_len = u32::from_be_bytes(signature_len_buf) as usize;
- signature.resize(signature_len, 0);
- self.stream.read_exact(&mut signature).await?;
-
- // Read key identifier from target to identify which public key to use
- let mut key_id_len_buf = [0u8; 4];
- self.stream.read_exact(&mut key_id_len_buf).await?;
- let key_id_len = u32::from_be_bytes(key_id_len_buf) as usize;
-
- let mut key_id_buf = vec![0u8; key_id_len];
- self.stream.read_exact(&mut key_id_buf).await?;
- let key_id = String::from_utf8(key_id_buf)
- .map_err(|e| TcpTargetError::Crypto(format!("Invalid key identifier: {}", e)))?;
-
- // Load appropriate public key
- let public_key_path = public_key_dir.as_ref().join(format!("{}.pem", key_id));
- if !public_key_path.exists() {
- return Ok(false);
- }
-
- let public_key_pem = tokio::fs::read_to_string(&public_key_path).await?;
-
- // Try to verify with different key types
- let verified = if let Ok(rsa_key) = RsaPublicKey::from_pkcs1_pem(&public_key_pem) {
- let padding = rsa::pkcs1v15::Pkcs1v15Sign::new::<sha2::Sha256>();
- rsa_key.verify(padding, &challenge, &signature).is_ok()
- } else if let Ok(ed25519_key) =
- VerifyingKey::from_bytes(&parse_ed25519_public_key(&public_key_pem))
- {
- if signature.len() == 64 {
- let sig_bytes: [u8; 64] = signature.as_slice().try_into().map_err(|_| {
- TcpTargetError::Crypto("Invalid signature length for Ed25519".to_string())
- })?;
- let sig = Signature::from_bytes(&sig_bytes);
- ed25519_key.verify(&challenge, &sig).is_ok()
- } else {
- false
- }
- } else if let Ok(dsa_key_info) = parse_dsa_public_key(&public_key_pem) {
- verify_dsa_signature(&dsa_key_info, &challenge, &signature)
- } else {
- false
- };
-
- Ok(verified)
- }
-
- /// Accepts a challenge from the target machine to verify connection security
- ///
- /// This method performs a cryptographic challenge-response authentication:
- /// 1. Receives a random 32-byte challenge from the target machine
- /// 2. Signs the challenge using the appropriate private key
- /// 3. Sends the digital signature back to the target machine
- /// 4. Sends the key identifier for public key verification
- ///
- /// # Arguments
- /// * `private_key_file` - Path to the private key file for signing
- /// * `verify_public_key` - Key identifier for public key verification
- ///
- /// # Returns
- /// * `Ok(true)` - Challenge response sent successfully
- /// * `Ok(false)` - Private key format not supported
- /// * `Err(TcpTargetError)` - Error during challenge response process
- pub async fn accept_challenge(
- &mut self,
- private_key_file: impl AsRef<Path>,
- verify_public_key: &str,
- ) -> Result<bool, TcpTargetError> {
- // Read challenge from initiator
- let mut challenge = [0u8; 32];
- self.stream.read_exact(&mut challenge).await?;
-
- // Load private key
- let private_key_pem = tokio::fs::read_to_string(&private_key_file).await?;
-
- // Sign the challenge with supported key types
- let signature = if let Ok(rsa_key) = RsaPrivateKey::from_pkcs1_pem(&private_key_pem) {
- let padding = rsa::pkcs1v15::Pkcs1v15Sign::new::<sha2::Sha256>();
- rsa_key.sign(padding, &challenge)?
- } else if let Ok(ed25519_key) = parse_ed25519_private_key(&private_key_pem) {
- ed25519_key.sign(&challenge).to_bytes().to_vec()
- } else if let Ok(dsa_key_info) = parse_dsa_private_key(&private_key_pem) {
- sign_with_dsa(&dsa_key_info, &challenge)?
- } else {
- return Ok(false);
- };
-
- // Send signature length and signature
- let signature_len = signature.len() as u32;
- self.stream.write_all(&signature_len.to_be_bytes()).await?;
- self.stream.flush().await?;
- self.stream.write_all(&signature).await?;
- self.stream.flush().await?;
-
- // Send key identifier for public key identification
- let key_id_bytes = verify_public_key.as_bytes();
- let key_id_len = key_id_bytes.len() as u32;
- self.stream.write_all(&key_id_len.to_be_bytes()).await?;
- self.stream.flush().await?;
- self.stream.write_all(key_id_bytes).await?;
- self.stream.flush().await?;
-
- Ok(true)
- }
-}
-
-/// Parse Ed25519 public key from PEM format
-fn parse_ed25519_public_key(pem: &str) -> [u8; 32] {
- // Robust parsing for Ed25519 public key using pem crate
- let mut key_bytes = [0u8; 32];
-
- if let Ok(pem_data) = pem::parse(pem)
- && pem_data.tag() == "PUBLIC KEY"
- && pem_data.contents().len() >= 32
- {
- let contents = pem_data.contents();
- key_bytes.copy_from_slice(&contents[contents.len() - 32..]);
- }
- key_bytes
-}
-
-/// Parse Ed25519 private key from PEM format
-fn parse_ed25519_private_key(pem: &str) -> Result<SigningKey, TcpTargetError> {
- if let Ok(pem_data) = pem::parse(pem)
- && pem_data.tag() == "PRIVATE KEY"
- && pem_data.contents().len() >= 32
- {
- let contents = pem_data.contents();
- let mut seed = [0u8; 32];
- seed.copy_from_slice(&contents[contents.len() - 32..]);
- return Ok(SigningKey::from_bytes(&seed));
- }
- Err(TcpTargetError::Crypto(
- "Invalid Ed25519 private key format".to_string(),
- ))
-}
-
-/// Parse DSA public key information from PEM
-fn parse_dsa_public_key(
- pem: &str,
-) -> Result<(&'static dyn signature::VerificationAlgorithm, Vec<u8>), TcpTargetError> {
- if let Ok(pem_data) = pem::parse(pem) {
- let contents = pem_data.contents().to_vec();
-
- // Try different DSA algorithms based on PEM tag
- match pem_data.tag() {
- "EC PUBLIC KEY" | "PUBLIC KEY" if pem.contains("ECDSA") || pem.contains("ecdsa") => {
- if pem.contains("P-256") {
- return Ok((&ECDSA_P256_SHA256_ASN1, contents));
- } else if pem.contains("P-384") {
- return Ok((&ECDSA_P384_SHA384_ASN1, contents));
- }
- }
- "RSA PUBLIC KEY" | "PUBLIC KEY" => {
- return Ok((&RSA_PKCS1_2048_8192_SHA256, contents));
- }
- _ => {}
- }
-
- // Default to RSA for unknown types
- return Ok((&RSA_PKCS1_2048_8192_SHA256, contents));
- }
- Err(TcpTargetError::Crypto(
- "Invalid DSA public key format".to_string(),
- ))
-}
-
-/// Parse DSA private key information from PEM
-fn parse_dsa_private_key(
- pem: &str,
-) -> Result<(&'static dyn signature::VerificationAlgorithm, Vec<u8>), TcpTargetError> {
- // For DSA, private key verification uses the same algorithm as public key
- parse_dsa_public_key(pem)
-}
-
-/// Verify DSA signature
-fn verify_dsa_signature(
- algorithm_and_key: &(&'static dyn signature::VerificationAlgorithm, Vec<u8>),
- message: &[u8],
- signature: &[u8],
-) -> bool {
- let (algorithm, key_bytes) = algorithm_and_key;
- let public_key = UnparsedPublicKey::new(*algorithm, key_bytes);
- public_key.verify(message, signature).is_ok()
-}
-
-/// Sign with DSA
-fn sign_with_dsa(
- algorithm_and_key: &(&'static dyn signature::VerificationAlgorithm, Vec<u8>),
- message: &[u8],
-) -> Result<Vec<u8>, TcpTargetError> {
- let (algorithm, key_bytes) = algorithm_and_key;
-
- // Handle different DSA/ECDSA algorithms by comparing algorithm identifiers
- // Since we can't directly compare trait objects, we use pointer comparison
- let algorithm_ptr = algorithm as *const _ as *const ();
- let ecdsa_p256_ptr = &ECDSA_P256_SHA256_ASN1 as *const _ as *const ();
- let ecdsa_p384_ptr = &ECDSA_P384_SHA384_ASN1 as *const _ as *const ();
-
- if algorithm_ptr == ecdsa_p256_ptr {
- let key_pair = EcdsaKeyPair::from_pkcs8(
- ECDSA_P256_SHA256_ASN1_SIGNING,
- key_bytes,
- &SystemRandom::new(),
- )
- .map_err(|e| {
- TcpTargetError::Crypto(format!("Failed to create ECDSA P-256 key pair: {}", e))
- })?;
-
- let signature = key_pair
- .sign(&SystemRandom::new(), message)
- .map_err(|e| TcpTargetError::Crypto(format!("ECDSA P-256 signing failed: {}", e)))?;
-
- Ok(signature.as_ref().to_vec())
- } else if algorithm_ptr == ecdsa_p384_ptr {
- let key_pair = EcdsaKeyPair::from_pkcs8(
- ECDSA_P384_SHA384_ASN1_SIGNING,
- key_bytes,
- &SystemRandom::new(),
- )
- .map_err(|e| {
- TcpTargetError::Crypto(format!("Failed to create ECDSA P-384 key pair: {}", e))
- })?;
-
- let signature = key_pair
- .sign(&SystemRandom::new(), message)
- .map_err(|e| TcpTargetError::Crypto(format!("ECDSA P-384 signing failed: {}", e)))?;
-
- Ok(signature.as_ref().to_vec())
- } else {
- // RSA or unsupported algorithm
- Err(TcpTargetError::Unsupported(
- "DSA/ECDSA signing not supported for this algorithm type".to_string(),
- ))
- }
}
diff --git a/crates/utils/tcp_connection/src/instance_challenge.rs b/crates/utils/tcp_connection/src/instance_challenge.rs
new file mode 100644
index 0000000..c1cf46f
--- /dev/null
+++ b/crates/utils/tcp_connection/src/instance_challenge.rs
@@ -0,0 +1,297 @@
+use std::path::Path;
+
+use rand::TryRngCore;
+use rsa::{
+ RsaPrivateKey, RsaPublicKey,
+ pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey},
+ sha2,
+};
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+
+use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
+use ring::rand::SystemRandom;
+use ring::signature::{
+ self, ECDSA_P256_SHA256_ASN1, ECDSA_P384_SHA384_ASN1, EcdsaKeyPair, RSA_PKCS1_2048_8192_SHA256,
+ UnparsedPublicKey,
+};
+
+use crate::{error::TcpTargetError, instance::ConnectionInstance};
+
+const ECDSA_P256_SHA256_ASN1_SIGNING: &signature::EcdsaSigningAlgorithm =
+ &signature::ECDSA_P256_SHA256_ASN1_SIGNING;
+const ECDSA_P384_SHA384_ASN1_SIGNING: &signature::EcdsaSigningAlgorithm =
+ &signature::ECDSA_P384_SHA384_ASN1_SIGNING;
+
+impl ConnectionInstance {
+ /// Initiates a challenge to the target machine to verify connection security
+ ///
+ /// This method performs a cryptographic challenge-response authentication:
+ /// 1. Generates a random 32-byte challenge
+ /// 2. Sends the challenge to the target machine
+ /// 3. Receives a digital signature of the challenge
+ /// 4. Verifies the signature using the appropriate public key
+ ///
+ /// # Arguments
+ /// * `public_key_dir` - Directory containing public key files for verification
+ ///
+ /// # Returns
+ /// * `Ok(true)` - Challenge verification successful
+ /// * `Ok(false)` - Challenge verification failed
+ /// * `Err(TcpTargetError)` - Error during challenge process
+ pub async fn challenge(
+ &mut self,
+ public_key_dir: impl AsRef<Path>,
+ ) -> Result<bool, TcpTargetError> {
+ // Generate random challenge
+ let mut challenge = [0u8; 32];
+ rand::rngs::OsRng
+ .try_fill_bytes(&mut challenge)
+ .map_err(|e| {
+ TcpTargetError::Crypto(format!("Failed to generate random challenge: {}", e))
+ })?;
+
+ // Send challenge to target
+ self.stream.write_all(&challenge).await?;
+ self.stream.flush().await?;
+
+ // Read signature from target
+ let mut signature = Vec::new();
+ let mut signature_len_buf = [0u8; 4];
+ self.stream.read_exact(&mut signature_len_buf).await?;
+
+ let signature_len = u32::from_be_bytes(signature_len_buf) as usize;
+ signature.resize(signature_len, 0);
+ self.stream.read_exact(&mut signature).await?;
+
+ // Read key identifier from target to identify which public key to use
+ let mut key_id_len_buf = [0u8; 4];
+ self.stream.read_exact(&mut key_id_len_buf).await?;
+ let key_id_len = u32::from_be_bytes(key_id_len_buf) as usize;
+
+ let mut key_id_buf = vec![0u8; key_id_len];
+ self.stream.read_exact(&mut key_id_buf).await?;
+ let key_id = String::from_utf8(key_id_buf)
+ .map_err(|e| TcpTargetError::Crypto(format!("Invalid key identifier: {}", e)))?;
+
+ // Load appropriate public key
+ let public_key_path = public_key_dir.as_ref().join(format!("{}.pem", key_id));
+ if !public_key_path.exists() {
+ return Ok(false);
+ }
+
+ let public_key_pem = tokio::fs::read_to_string(&public_key_path).await?;
+
+ // Try to verify with different key types
+ let verified = if let Ok(rsa_key) = RsaPublicKey::from_pkcs1_pem(&public_key_pem) {
+ let padding = rsa::pkcs1v15::Pkcs1v15Sign::new::<sha2::Sha256>();
+ rsa_key.verify(padding, &challenge, &signature).is_ok()
+ } else if let Ok(ed25519_key) =
+ VerifyingKey::from_bytes(&parse_ed25519_public_key(&public_key_pem))
+ {
+ if signature.len() == 64 {
+ let sig_bytes: [u8; 64] = signature.as_slice().try_into().map_err(|_| {
+ TcpTargetError::Crypto("Invalid signature length for Ed25519".to_string())
+ })?;
+ let sig = Signature::from_bytes(&sig_bytes);
+ ed25519_key.verify(&challenge, &sig).is_ok()
+ } else {
+ false
+ }
+ } else if let Ok(dsa_key_info) = parse_dsa_public_key(&public_key_pem) {
+ verify_dsa_signature(&dsa_key_info, &challenge, &signature)
+ } else {
+ false
+ };
+
+ Ok(verified)
+ }
+
+ /// Accepts a challenge from the target machine to verify connection security
+ ///
+ /// This method performs a cryptographic challenge-response authentication:
+ /// 1. Receives a random 32-byte challenge from the target machine
+ /// 2. Signs the challenge using the appropriate private key
+ /// 3. Sends the digital signature back to the target machine
+ /// 4. Sends the key identifier for public key verification
+ ///
+ /// # Arguments
+ /// * `private_key_file` - Path to the private key file for signing
+ /// * `verify_public_key` - Key identifier for public key verification
+ ///
+ /// # Returns
+ /// * `Ok(true)` - Challenge response sent successfully
+ /// * `Ok(false)` - Private key format not supported
+ /// * `Err(TcpTargetError)` - Error during challenge response process
+ pub async fn accept_challenge(
+ &mut self,
+ private_key_file: impl AsRef<Path>,
+ verify_public_key: &str,
+ ) -> Result<bool, TcpTargetError> {
+ // Read challenge from initiator
+ let mut challenge = [0u8; 32];
+ self.stream.read_exact(&mut challenge).await?;
+
+ // Load private key
+ let private_key_pem = tokio::fs::read_to_string(&private_key_file).await?;
+
+ // Sign the challenge with supported key types
+ let signature = if let Ok(rsa_key) = RsaPrivateKey::from_pkcs1_pem(&private_key_pem) {
+ let padding = rsa::pkcs1v15::Pkcs1v15Sign::new::<sha2::Sha256>();
+ rsa_key.sign(padding, &challenge)?
+ } else if let Ok(ed25519_key) = parse_ed25519_private_key(&private_key_pem) {
+ ed25519_key.sign(&challenge).to_bytes().to_vec()
+ } else if let Ok(dsa_key_info) = parse_dsa_private_key(&private_key_pem) {
+ sign_with_dsa(&dsa_key_info, &challenge)?
+ } else {
+ return Ok(false);
+ };
+
+ // Send signature length and signature
+ let signature_len = signature.len() as u32;
+ self.stream.write_all(&signature_len.to_be_bytes()).await?;
+ self.stream.flush().await?;
+ self.stream.write_all(&signature).await?;
+ self.stream.flush().await?;
+
+ // Send key identifier for public key identification
+ let key_id_bytes = verify_public_key.as_bytes();
+ let key_id_len = key_id_bytes.len() as u32;
+ self.stream.write_all(&key_id_len.to_be_bytes()).await?;
+ self.stream.flush().await?;
+ self.stream.write_all(key_id_bytes).await?;
+ self.stream.flush().await?;
+
+ Ok(true)
+ }
+}
+
+/// Parse Ed25519 public key from PEM format
+fn parse_ed25519_public_key(pem: &str) -> [u8; 32] {
+ // Robust parsing for Ed25519 public key using pem crate
+ let mut key_bytes = [0u8; 32];
+
+ if let Ok(pem_data) = pem::parse(pem)
+ && pem_data.tag() == "PUBLIC KEY"
+ && pem_data.contents().len() >= 32
+ {
+ let contents = pem_data.contents();
+ key_bytes.copy_from_slice(&contents[contents.len() - 32..]);
+ }
+ key_bytes
+}
+
+/// Parse Ed25519 private key from PEM format
+fn parse_ed25519_private_key(pem: &str) -> Result<SigningKey, TcpTargetError> {
+ if let Ok(pem_data) = pem::parse(pem)
+ && pem_data.tag() == "PRIVATE KEY"
+ && pem_data.contents().len() >= 32
+ {
+ let contents = pem_data.contents();
+ let mut seed = [0u8; 32];
+ seed.copy_from_slice(&contents[contents.len() - 32..]);
+ return Ok(SigningKey::from_bytes(&seed));
+ }
+ Err(TcpTargetError::Crypto(
+ "Invalid Ed25519 private key format".to_string(),
+ ))
+}
+
+/// Parse DSA public key information from PEM
+fn parse_dsa_public_key(
+ pem: &str,
+) -> Result<(&'static dyn signature::VerificationAlgorithm, Vec<u8>), TcpTargetError> {
+ if let Ok(pem_data) = pem::parse(pem) {
+ let contents = pem_data.contents().to_vec();
+
+ // Try different DSA algorithms based on PEM tag
+ match pem_data.tag() {
+ "EC PUBLIC KEY" | "PUBLIC KEY" if pem.contains("ECDSA") || pem.contains("ecdsa") => {
+ if pem.contains("P-256") {
+ return Ok((&ECDSA_P256_SHA256_ASN1, contents));
+ } else if pem.contains("P-384") {
+ return Ok((&ECDSA_P384_SHA384_ASN1, contents));
+ }
+ }
+ "RSA PUBLIC KEY" | "PUBLIC KEY" => {
+ return Ok((&RSA_PKCS1_2048_8192_SHA256, contents));
+ }
+ _ => {}
+ }
+
+ // Default to RSA for unknown types
+ return Ok((&RSA_PKCS1_2048_8192_SHA256, contents));
+ }
+ Err(TcpTargetError::Crypto(
+ "Invalid DSA public key format".to_string(),
+ ))
+}
+
+/// Parse DSA private key information from PEM
+fn parse_dsa_private_key(
+ pem: &str,
+) -> Result<(&'static dyn signature::VerificationAlgorithm, Vec<u8>), TcpTargetError> {
+ // For DSA, private key verification uses the same algorithm as public key
+ parse_dsa_public_key(pem)
+}
+
+/// Verify DSA signature
+fn verify_dsa_signature(
+ algorithm_and_key: &(&'static dyn signature::VerificationAlgorithm, Vec<u8>),
+ message: &[u8],
+ signature: &[u8],
+) -> bool {
+ let (algorithm, key_bytes) = algorithm_and_key;
+ let public_key = UnparsedPublicKey::new(*algorithm, key_bytes);
+ public_key.verify(message, signature).is_ok()
+}
+
+/// Sign with DSA
+fn sign_with_dsa(
+ algorithm_and_key: &(&'static dyn signature::VerificationAlgorithm, Vec<u8>),
+ message: &[u8],
+) -> Result<Vec<u8>, TcpTargetError> {
+ let (algorithm, key_bytes) = algorithm_and_key;
+
+ // Handle different DSA/ECDSA algorithms by comparing algorithm identifiers
+ // Since we can't directly compare trait objects, we use pointer comparison
+ let algorithm_ptr = algorithm as *const _ as *const ();
+ let ecdsa_p256_ptr = &ECDSA_P256_SHA256_ASN1 as *const _ as *const ();
+ let ecdsa_p384_ptr = &ECDSA_P384_SHA384_ASN1 as *const _ as *const ();
+
+ if algorithm_ptr == ecdsa_p256_ptr {
+ let key_pair = EcdsaKeyPair::from_pkcs8(
+ ECDSA_P256_SHA256_ASN1_SIGNING,
+ key_bytes,
+ &SystemRandom::new(),
+ )
+ .map_err(|e| {
+ TcpTargetError::Crypto(format!("Failed to create ECDSA P-256 key pair: {}", e))
+ })?;
+
+ let signature = key_pair
+ .sign(&SystemRandom::new(), message)
+ .map_err(|e| TcpTargetError::Crypto(format!("ECDSA P-256 signing failed: {}", e)))?;
+
+ Ok(signature.as_ref().to_vec())
+ } else if algorithm_ptr == ecdsa_p384_ptr {
+ let key_pair = EcdsaKeyPair::from_pkcs8(
+ ECDSA_P384_SHA384_ASN1_SIGNING,
+ key_bytes,
+ &SystemRandom::new(),
+ )
+ .map_err(|e| {
+ TcpTargetError::Crypto(format!("Failed to create ECDSA P-384 key pair: {}", e))
+ })?;
+
+ let signature = key_pair
+ .sign(&SystemRandom::new(), message)
+ .map_err(|e| TcpTargetError::Crypto(format!("ECDSA P-384 signing failed: {}", e)))?;
+
+ Ok(signature.as_ref().to_vec())
+ } else {
+ // RSA or unsupported algorithm
+ Err(TcpTargetError::Unsupported(
+ "DSA/ECDSA signing not supported for this algorithm type".to_string(),
+ ))
+ }
+}
diff --git a/crates/utils/tcp_connection/src/lib.rs b/crates/utils/tcp_connection/src/lib.rs
index a5b5c20..6a2e599 100644
--- a/crates/utils/tcp_connection/src/lib.rs
+++ b/crates/utils/tcp_connection/src/lib.rs
@@ -1,4 +1,6 @@
#[allow(dead_code)]
pub mod instance;
+pub mod instance_challenge;
+
pub mod error;
diff --git a/crates/vcs/src/data/sheet.rs b/crates/vcs/src/data/sheet.rs
index 95599ff..a6220c9 100644
--- a/crates/vcs/src/data/sheet.rs
+++ b/crates/vcs/src/data/sheet.rs
@@ -97,8 +97,8 @@ impl<'a> Sheet<'a> {
Ok(())
}
- /// Remove an input package from the sheet
- pub fn remove_input(&mut self, input_name: &InputName) -> Option<InputPackage> {
+ /// Deny and remove an input package from the sheet
+ pub fn deny_input(&mut self, input_name: &InputName) -> Option<InputPackage> {
self.data
.inputs
.iter()
@@ -106,14 +106,127 @@ impl<'a> Sheet<'a> {
.map(|pos| self.data.inputs.remove(pos))
}
- /// Add a mapping entry to the sheet
- pub fn add_mapping(&mut self, sheet_path: SheetPathBuf, virtual_file_id: VirtualFileId) {
- self.data.mapping.insert(sheet_path, virtual_file_id);
+ /// Accept an input package and insert to the sheet
+ pub fn accept_import(
+ &mut self,
+ input_name: &InputName,
+ insert_to: &SheetPathBuf,
+ ) -> Result<(), std::io::Error> {
+ // Remove inputs
+ let input = self
+ .inputs()
+ .iter()
+ .position(|input| input.name == *input_name)
+ .map(|pos| self.data.inputs.remove(pos));
+
+ // Ensure input is not empty
+ let Some(input) = input else {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ "Empty inputs.",
+ ));
+ };
+
+ // Insert to sheet
+ for (relative_path, virtual_file_id) in input.files {
+ let _ = self.add_mapping(insert_to.join(relative_path), virtual_file_id);
+ }
+
+ Ok(())
+ }
+
+ /// Add (or Edit) a mapping entry to the sheet
+ ///
+ /// This operation performs safety checks to ensure the member has the right to add the mapping:
+ /// 1. If the virtual file ID doesn't exist in the vault, the mapping is added directly
+ /// 2. If the virtual file exists, check if the member has edit rights to the virtual file
+ /// 3. If member has edit rights, the mapping is not allowed to be modified and returns an error
+ /// 4. If member doesn't have edit rights, the mapping is allowed (member is giving up the file)
+ ///
+ /// Note: Full validation adds overhead - avoid frequent calls
+ pub async fn add_mapping(
+ &mut self,
+ sheet_path: SheetPathBuf,
+ virtual_file_id: VirtualFileId,
+ ) -> Result<(), std::io::Error> {
+ // Check if the virtual file exists in the vault
+ if self.vault_reference.virtual_file(&virtual_file_id).is_err() {
+ // Virtual file doesn't exist, add the mapping directly
+ self.data.mapping.insert(sheet_path, virtual_file_id);
+ return Ok(());
+ }
+
+ // Check if the holder has edit rights to the virtual file
+ match self
+ .vault_reference
+ .has_virtual_file_edit_right(self.holder(), &virtual_file_id)
+ .await
+ {
+ Ok(false) => {
+ // Holder doesn't have rights, add the mapping (member is giving up the file)
+ self.data.mapping.insert(sheet_path, virtual_file_id);
+ Ok(())
+ }
+ Ok(true) => {
+ // Holder has edit rights, don't allow modifying the mapping
+ Err(std::io::Error::new(
+ std::io::ErrorKind::PermissionDenied,
+ "Member has edit rights to the virtual file, cannot modify mapping",
+ ))
+ }
+ Err(_) => {
+ // Error checking rights, don't allow modifying the mapping
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Failed to check virtual file edit rights",
+ ))
+ }
+ }
}
/// Remove a mapping entry from the sheet
- pub fn remove_mapping(&mut self, sheet_path: &SheetPathBuf) -> Option<VirtualFileId> {
- self.data.mapping.remove(sheet_path)
+ ///
+ /// This operation performs safety checks to ensure the member has the right to remove the mapping:
+ /// 1. Member must NOT have edit rights to the virtual file to release it (ensuring clear ownership)
+ /// 2. If the virtual file doesn't exist, the mapping is removed but no ID is returned
+ /// 3. If member has no edit rights and the file exists, returns the removed virtual file ID
+ ///
+ /// Note: Full validation adds overhead - avoid frequent calls
+ pub async fn remove_mapping(&mut self, sheet_path: &SheetPathBuf) -> Option<VirtualFileId> {
+ let virtual_file_id = match self.data.mapping.get(sheet_path) {
+ Some(id) => id,
+ None => {
+ // The mapping entry doesn't exist, nothing to remove
+ return None;
+ }
+ };
+
+ // Check if the virtual file exists in the vault
+ if self.vault_reference.virtual_file(virtual_file_id).is_err() {
+ // Virtual file doesn't exist, remove the mapping and return None
+ self.data.mapping.remove(sheet_path);
+ return None;
+ }
+
+ // Check if the holder has edit rights to the virtual file
+ match self
+ .vault_reference
+ .has_virtual_file_edit_right(self.holder(), virtual_file_id)
+ .await
+ {
+ Ok(false) => {
+ // Holder doesn't have rights, remove and return the virtual file ID
+ self.data.mapping.remove(sheet_path)
+ }
+ Ok(true) => {
+ // Holder has edit rights, don't remove the mapping
+ None
+ }
+ Err(_) => {
+ // Error checking rights, don't remove the mapping
+ None
+ }
+ }
}
/// Persist the sheet to disk
@@ -178,9 +291,7 @@ impl<'a> Sheet<'a> {
}
// Find the longest common prefix among all paths
- let common_prefix = paths.iter().skip(1).fold(paths[0].clone(), |prefix, path| {
- Self::common_path_prefix(prefix, path)
- });
+ let common_prefix = Self::find_longest_common_prefix(paths);
// Create output files with optimized relative paths
let files = paths
@@ -209,16 +320,28 @@ impl<'a> Sheet<'a> {
})
}
- /// Helper function to find common path prefix between two paths
- fn common_path_prefix(path1: impl Into<PathBuf>, path2: impl Into<PathBuf>) -> PathBuf {
- let path1 = path1.into();
- let path2 = path2.into();
+ /// Helper function to find the longest common prefix among all paths
+ fn find_longest_common_prefix(paths: &[SheetPathBuf]) -> PathBuf {
+ if paths.is_empty() {
+ return PathBuf::new();
+ }
+
+ let first_path = &paths[0];
+ let mut common_components = Vec::new();
+
+ for (component_idx, first_component) in first_path.components().enumerate() {
+ for path in paths.iter().skip(1) {
+ if let Some(component) = path.components().nth(component_idx) {
+ if component != first_component {
+ return common_components.into_iter().collect();
+ }
+ } else {
+ return common_components.into_iter().collect();
+ }
+ }
+ common_components.push(first_component);
+ }
- path1
- .components()
- .zip(path2.components())
- .take_while(|(a, b)| a == b)
- .map(|(comp, _)| comp)
- .collect()
+ common_components.into_iter().collect()
}
}
diff --git a/crates/vcs/src/data/vault/virtual_file.rs b/crates/vcs/src/data/vault/virtual_file.rs
index 83b1c82..fe83594 100644
--- a/crates/vcs/src/data/vault/virtual_file.rs
+++ b/crates/vcs/src/data/vault/virtual_file.rs
@@ -225,7 +225,7 @@ impl Vault {
// Create metadata
let mut meta = VirtualFileMeta {
current_version: FIRST_VERSION.to_string(),
- hold_member: String::default(),
+ hold_member: member_id.clone(), // The holder of the newly created virtual file is the creator by default
version_description,
histories: Vec::default(),
};
@@ -244,6 +244,8 @@ impl Vault {
}
fs::rename(receive_path, move_path).await?;
+ //
+
Ok(new_id)
}
Err(e) => {
diff --git a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs b/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
index 8abcc4d..3b038a0 100644
--- a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
+++ b/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
@@ -58,8 +58,12 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
let main_rs_id = VirtualFileId::new();
let lib_rs_id = VirtualFileId::new();
- sheet.add_mapping(main_rs_path.clone(), main_rs_id.clone());
- sheet.add_mapping(lib_rs_path.clone(), lib_rs_id.clone());
+ sheet
+ .add_mapping(main_rs_path.clone(), main_rs_id.clone())
+ .await?;
+ sheet
+ .add_mapping(lib_rs_path.clone(), lib_rs_id.clone())
+ .await?;
// Use output_mappings to generate the InputPackage
let paths = vec![main_rs_path, lib_rs_path];
@@ -83,7 +87,10 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
// Test 3: Add mapping entries
let mapping_path = vcs::data::sheet::SheetPathBuf::from("output/build.exe");
let virtual_file_id = VirtualFileId::new();
- sheet.add_mapping(mapping_path.clone(), virtual_file_id.clone());
+
+ sheet
+ .add_mapping(mapping_path.clone(), virtual_file_id.clone())
+ .await?;
// Verify mapping was added
assert_eq!(sheet.mapping().len(), 3);
@@ -100,7 +107,7 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
// Test 5: Remove input package
let mut sheet_for_removal = vault.sheet(&sheet_name).await?;
- let removed_input = sheet_for_removal.remove_input(&input_name);
+ let removed_input = sheet_for_removal.deny_input(&input_name);
assert!(removed_input.is_some());
let removed_input = removed_input.unwrap();
assert_eq!(removed_input.name, input_name);
@@ -108,8 +115,8 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
assert_eq!(sheet_for_removal.inputs().len(), 0);
// Test 6: Remove mapping entry
- let removed_virtual_file_id = sheet_for_removal.remove_mapping(&mapping_path);
- assert_eq!(removed_virtual_file_id, Some(virtual_file_id));
+ let _removed_virtual_file_id = sheet_for_removal.remove_mapping(&mapping_path).await;
+ // Don't check the return value since it depends on virtual file existence
assert_eq!(sheet_for_removal.mapping().len(), 2);
// Test 7: List all sheets in vault
@@ -263,8 +270,12 @@ async fn test_sheet_data_serialization() -> Result<(), std::io::Error> {
let main_rs_id = VirtualFileId::new();
let lib_rs_id = VirtualFileId::new();
- sheet.add_mapping(main_rs_path.clone(), main_rs_id.clone());
- sheet.add_mapping(lib_rs_path.clone(), lib_rs_id.clone());
+ sheet
+ .add_mapping(main_rs_path.clone(), main_rs_id.clone())
+ .await?;
+ sheet
+ .add_mapping(lib_rs_path.clone(), lib_rs_id.clone())
+ .await?;
// Use output_mappings to generate the InputPackage
let paths = vec![main_rs_path, lib_rs_path];
@@ -272,10 +283,14 @@ async fn test_sheet_data_serialization() -> Result<(), std::io::Error> {
sheet.add_input(input_package)?;
// Add some mappings
- sheet.add_mapping(
- vcs::data::sheet::SheetPathBuf::from("output/build.exe"),
- VirtualFileId::new(),
- );
+ let build_exe_id = VirtualFileId::new();
+
+ sheet
+ .add_mapping(
+ vcs::data::sheet::SheetPathBuf::from("output/build.exe"),
+ build_exe_id,
+ )
+ .await?;
// Persist the sheet
sheet.persist().await?;
diff --git a/src/lib.rs b/src/lib.rs
index 3473dc9..3a6c652 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27,17 +27,3 @@ pub mod utils {
pub use cfg_file::*;
}
}
-
-pub mod prelude {
- #[cfg(feature = "vcs")]
- pub use super::vcs::*;
-
- #[cfg(feature = "tcp_connection")]
- pub use super::utils::tcp_connection::*;
-
- #[cfg(feature = "string_proc")]
- pub use super::utils::string_proc::*;
-
- #[cfg(feature = "cfg_file")]
- pub use super::utils::cfg_file::*;
-}