Compare commits
No commits in common. "a38845ef0b016a6b360cc682ddc3029481959566" and "dc3609bd75f9b55ba1595ee365b86f10a3b30098" have entirely different histories.
a38845ef0b
...
dc3609bd75
27 changed files with 262 additions and 1555 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -632,13 +632,16 @@ dependencies = [
|
|||
name = "sharenet-passport"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"bip39",
|
||||
"chacha20poly1305",
|
||||
"clap",
|
||||
"ed25519-dalek",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"rand",
|
||||
"rand_core",
|
||||
"rpassword",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"sha2",
|
||||
|
|
@ -647,18 +650,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharenet-passport-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"clap",
|
||||
"hex",
|
||||
"rpassword",
|
||||
"sharenet-passport",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
|
|
|
|||
22
Cargo.toml
22
Cargo.toml
|
|
@ -1,12 +1,9 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"libs/sharenet-passport",
|
||||
"sharenet-passport-cli"
|
||||
]
|
||||
resolver = "2"
|
||||
[package]
|
||||
name = "sharenet-passport"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Common dependencies shared across workspace members
|
||||
[dependencies]
|
||||
bip39 = "2.1"
|
||||
ed25519-dalek = { version = "2.1", features = ["serde"] }
|
||||
chacha20poly1305 = "0.10"
|
||||
|
|
@ -16,8 +13,13 @@ rand = "0.8"
|
|||
rand_core = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_cbor = "0.11"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
zeroize = { version = "1.7", features = ["zeroize_derive"] }
|
||||
hex = "0.4"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
rpassword = "7.2"
|
||||
hex = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5"
|
||||
hex = "0.4"
|
||||
tempfile = "3.8"
|
||||
41
README.md
41
README.md
|
|
@ -24,7 +24,7 @@ cd sharenet_passport_creator
|
|||
cargo build --release
|
||||
|
||||
# Run the CLI
|
||||
./target/release/sharenet-passport-cli-cli --help
|
||||
./target/release/sharenet-passport --help
|
||||
```
|
||||
|
||||
## Usage Guide
|
||||
|
|
@ -34,7 +34,7 @@ cargo build --release
|
|||
Create a new Sharenet Passport with a secure recovery phrase:
|
||||
|
||||
```bash
|
||||
./target/release/sharenet-passport-cli create --output my-passport.spf
|
||||
./target/release/sharenet-passport create my-passport.spf
|
||||
```
|
||||
|
||||
You'll be prompted to:
|
||||
|
|
@ -54,7 +54,7 @@ The tool will then:
|
|||
If you have a 24-word recovery phrase, you can import it to create a new .spf file:
|
||||
|
||||
```bash
|
||||
./target/release/sharenet-passport-cli import-recovery --output recovered-passport.spf
|
||||
./target/release/sharenet-passport import-recovery recovered-passport.spf
|
||||
```
|
||||
|
||||
You'll be prompted to:
|
||||
|
|
@ -67,10 +67,10 @@ Import an existing .spf file (useful for re-encryption or verification):
|
|||
|
||||
```bash
|
||||
# Import without re-encryption
|
||||
./target/release/sharenet-passport-cli import-file existing.spf
|
||||
./target/release/sharenet-passport import-file existing.spf
|
||||
|
||||
# Import and re-encrypt to new file
|
||||
./target/release/sharenet-passport-cli import-file existing.spf --output new-passport.spf
|
||||
./target/release/sharenet-passport import-file existing.spf new-passport.spf
|
||||
```
|
||||
|
||||
You'll be prompted for the access password of the existing file.
|
||||
|
|
@ -80,7 +80,7 @@ You'll be prompted for the access password of the existing file.
|
|||
Export a passport with a new access password:
|
||||
|
||||
```bash
|
||||
./target/release/sharenet-passport-cli export old-passport.spf --output new-passport.spf
|
||||
./target/release/sharenet-passport export old-passport.spf new-passport.spf
|
||||
```
|
||||
|
||||
You'll be prompted to:
|
||||
|
|
@ -92,7 +92,7 @@ You'll be prompted to:
|
|||
Display information about a .spf file:
|
||||
|
||||
```bash
|
||||
./target/release/sharenet-passport-cli info my-passport.spf
|
||||
./target/release/sharenet-passport info my-passport.spf
|
||||
```
|
||||
|
||||
Shows:
|
||||
|
|
@ -105,7 +105,7 @@ Shows:
|
|||
Sign a message using your passport's private key:
|
||||
|
||||
```bash
|
||||
./target/release/sharenet-passport-cli sign my-passport.spf "Hello, Sharenet!"
|
||||
./target/release/sharenet-passport sign my-passport.spf "Hello, Sharenet!"
|
||||
```
|
||||
|
||||
Output includes:
|
||||
|
|
@ -144,30 +144,17 @@ Your 24-word recovery phrase is the master key to your identity:
|
|||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run tests for all workspace crates
|
||||
cargo test --workspace
|
||||
|
||||
# Run tests for specific crates
|
||||
cargo test -p sharenet-passport # Library crate tests
|
||||
cargo test -p sharenet-passport-cli # CLI crate tests
|
||||
cargo test
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
sharenet_passport_creator/
|
||||
├── libs/
|
||||
│ └── sharenet-passport/ # Core library crate
|
||||
│ ├── src/
|
||||
│ │ ├── domain/ # Core entities and traits
|
||||
│ │ ├── application/ # Use cases and business logic
|
||||
│ │ └── infrastructure/ # Crypto and storage implementations
|
||||
│ └── Cargo.toml
|
||||
├── sharenet-passport-cli/ # CLI crate
|
||||
│ ├── src/
|
||||
│ │ └── cli/ # Command-line interface
|
||||
│ └── Cargo.toml
|
||||
└── Cargo.toml # Workspace configuration
|
||||
src/
|
||||
├── domain/ # Core entities and traits
|
||||
├── application/ # Use cases and business logic
|
||||
├── infrastructure/ # Crypto and storage implementations
|
||||
└── cli/ # Command-line interface
|
||||
```
|
||||
|
||||
### Architecture
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
[package]
|
||||
name = "sharenet-passport"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Core library for Sharenet Passport creation and management"
|
||||
authors = ["Your Name <your.email@example.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://git.sharenet.sh/your-org/sharenet-passport"
|
||||
|
||||
[dependencies]
|
||||
bip39 = "2.1"
|
||||
ed25519-dalek = { version = "2.1", features = ["serde"] }
|
||||
chacha20poly1305 = "0.10"
|
||||
hkdf = "0.12"
|
||||
sha2 = "0.10"
|
||||
rand = "0.8"
|
||||
rand_core = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_cbor = "0.11"
|
||||
thiserror = "1.0"
|
||||
zeroize = { version = "1.7", features = ["zeroize_derive"] }
|
||||
hex = "0.4"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"] # Support both native and WASM
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [] # Standard library support
|
||||
alloc = [] # No-std with alloc support
|
||||
wasm = ["alloc"] # WASM target support
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.8"
|
||||
|
||||
[publish]
|
||||
registry = "sharenet"
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#[cfg(feature = "std")]
|
||||
pub mod crypto;
|
||||
#[cfg(feature = "std")]
|
||||
pub mod storage;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
#[cfg(feature = "std")]
|
||||
pub use storage::FileSystemStorage;
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod crypto_test;
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod storage_test;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
//! Sharenet Passport Core Library
|
||||
//!
|
||||
//! This library provides core functionality for creating, managing, and verifying
|
||||
//! Sharenet Passports using the .spf file format.
|
||||
|
||||
pub mod domain;
|
||||
pub mod application;
|
||||
pub mod infrastructure;
|
||||
|
||||
// Public API surface
|
||||
pub use domain::entities::{Passport, RecoveryPhrase, PassportFile, PublicKey, PrivateKey, Did, Seed};
|
||||
pub use domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor, FileStorage};
|
||||
pub use domain::error::DomainError;
|
||||
|
||||
pub use application::use_cases::{
|
||||
CreatePassportUseCase,
|
||||
ImportFromRecoveryUseCase,
|
||||
ImportFromFileUseCase,
|
||||
ExportPassportUseCase,
|
||||
SignCardUseCase
|
||||
};
|
||||
pub use application::error::ApplicationError;
|
||||
|
||||
pub use infrastructure::{
|
||||
Bip39MnemonicGenerator,
|
||||
Ed25519KeyDeriver,
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage
|
||||
};
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "sharenet-passport-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "CLI interface for Sharenet Passport"
|
||||
authors = ["Your Name <your.email@example.com>"]
|
||||
|
||||
[dependencies]
|
||||
sharenet-passport = { path = "../libs/sharenet-passport" }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
rpassword = "7.2"
|
||||
hex = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5"
|
||||
tempfile = "3.8"
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
use sharenet_passport::{
|
||||
application::use_cases::*,
|
||||
infrastructure::*,
|
||||
ApplicationError,
|
||||
FileStorage,
|
||||
};
|
||||
use rpassword::prompt_password;
|
||||
use hex;
|
||||
|
||||
pub struct CliInterface;
|
||||
|
||||
impl CliInterface {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn handle_create(&self, output: &str) -> Result<(), ApplicationError> {
|
||||
let password = prompt_password("Enter password for new passport: ").unwrap();
|
||||
let confirm_password = prompt_password("Confirm password: ").unwrap();
|
||||
|
||||
if password != confirm_password {
|
||||
return Err(ApplicationError::UseCaseError("Passwords do not match".to_string()));
|
||||
}
|
||||
|
||||
let use_case = CreatePassportUseCase::new(
|
||||
Bip39MnemonicGenerator,
|
||||
Ed25519KeyDeriver,
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage,
|
||||
);
|
||||
|
||||
let (passport, recovery_phrase) = use_case.execute(&password, output)?;
|
||||
|
||||
println!("✅ Passport created successfully!");
|
||||
println!("📄 Saved to: {}", output);
|
||||
println!("🔑 Public Key: {}", hex::encode(&passport.public_key().0));
|
||||
println!("🆔 DID: {}", passport.did().as_str());
|
||||
println!("\n📝 IMPORTANT: Save your recovery phrase in a secure location!");
|
||||
println!("Recovery phrase: {}", recovery_phrase.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_import_recovery(&self, output: &str) -> Result<(), ApplicationError> {
|
||||
println!("Enter your 24-word recovery phrase:");
|
||||
let mut recovery_words = Vec::new();
|
||||
|
||||
for i in 1..=24 {
|
||||
let word = prompt_password(&format!("Word {}: ", i)).unwrap();
|
||||
recovery_words.push(word);
|
||||
}
|
||||
|
||||
let password = prompt_password("Enter new password for passport file: ").unwrap();
|
||||
let confirm_password = prompt_password("Confirm password: ").unwrap();
|
||||
|
||||
if password != confirm_password {
|
||||
return Err(ApplicationError::UseCaseError("Passwords do not match".to_string()));
|
||||
}
|
||||
|
||||
let use_case = ImportFromRecoveryUseCase::new(
|
||||
Bip39MnemonicGenerator,
|
||||
Ed25519KeyDeriver,
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage,
|
||||
);
|
||||
|
||||
let passport = use_case.execute(&recovery_words, &password, output)?;
|
||||
|
||||
println!("✅ Passport imported successfully!");
|
||||
println!("📄 Saved to: {}", output);
|
||||
println!("🔑 Public Key: {}", hex::encode(&passport.public_key().0));
|
||||
println!("🆔 DID: {}", passport.did().as_str());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_import_file(&self, input: &str, output: Option<&str>) -> Result<(), ApplicationError> {
|
||||
let password = prompt_password("Enter password for passport file: ").unwrap();
|
||||
|
||||
let use_case = ImportFromFileUseCase::new(
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage,
|
||||
);
|
||||
|
||||
let passport = use_case.execute(input, &password, output)?;
|
||||
|
||||
println!("✅ Passport imported successfully!");
|
||||
if let Some(output_path) = output {
|
||||
println!("📄 Re-encrypted to: {}", output_path);
|
||||
}
|
||||
println!("🔑 Public Key: {}", hex::encode(&passport.public_key().0));
|
||||
println!("🆔 DID: {}", passport.did().as_str());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_export(&self, input: &str, output: &str) -> Result<(), ApplicationError> {
|
||||
let password = prompt_password("Enter password for passport file: ").unwrap();
|
||||
let new_password = prompt_password("Enter new password for exported file: ").unwrap();
|
||||
let confirm_password = prompt_password("Confirm new password: ").unwrap();
|
||||
|
||||
if new_password != confirm_password {
|
||||
return Err(ApplicationError::UseCaseError("Passwords do not match".to_string()));
|
||||
}
|
||||
|
||||
// First import to get the passport
|
||||
let import_use_case = ImportFromFileUseCase::new(
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage,
|
||||
);
|
||||
|
||||
let passport = import_use_case.execute(input, &password, None)?;
|
||||
|
||||
// Then export with new password
|
||||
let export_use_case = ExportPassportUseCase::new(
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage,
|
||||
);
|
||||
|
||||
export_use_case.execute(&passport, &new_password, output)?;
|
||||
|
||||
println!("✅ Passport exported successfully!");
|
||||
println!("📄 Saved to: {}", output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_info(&self, file: &str) -> Result<(), ApplicationError> {
|
||||
let passport_file = FileSystemStorage.load(file)
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to load file: {}", e)))?;
|
||||
|
||||
println!("📄 Passport File Information:");
|
||||
println!(" File: {}", file);
|
||||
println!(" Version: {}", passport_file.version);
|
||||
println!(" Created: {}", passport_file.created_at);
|
||||
println!(" DID: {}", passport_file.did);
|
||||
println!(" Public Key: {}", hex::encode(&passport_file.public_key));
|
||||
println!(" KDF: {}", passport_file.kdf);
|
||||
println!(" Cipher: {}", passport_file.cipher);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_sign(&self, file: &str, message: &str) -> Result<(), ApplicationError> {
|
||||
let password = prompt_password("Enter password for passport file: ").unwrap();
|
||||
|
||||
let import_use_case = ImportFromFileUseCase::new(
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage,
|
||||
);
|
||||
|
||||
let passport = import_use_case.execute(file, &password, None)?;
|
||||
|
||||
let sign_use_case = SignCardUseCase::new();
|
||||
let signature = sign_use_case.execute(&passport, message)?;
|
||||
|
||||
println!("✅ Message signed successfully!");
|
||||
println!("📝 Message: {}", message);
|
||||
println!("🔏 Signature: {}", hex::encode(&signature));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1231
sharenet_spec.md
1231
sharenet_spec.md
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,10 @@ pub enum ApplicationError {
|
|||
#[error("Use case error: {0}")]
|
||||
UseCaseError(String),
|
||||
|
||||
#[error("Invalid input: {0}")]
|
||||
InvalidInput(String),
|
||||
|
||||
|
||||
#[error("Domain error: {0}")]
|
||||
DomainError(#[from] crate::domain::error::DomainError),
|
||||
}
|
||||
|
|
@ -2,4 +2,4 @@ pub mod use_cases;
|
|||
pub mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
mod use_cases_test;
|
||||
pub mod use_cases_test;
|
||||
216
src/cli/interface.rs
Normal file
216
src/cli/interface.rs
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
use crate::application::use_cases::*;
|
||||
use crate::application::error::ApplicationError;
|
||||
use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
use crate::infrastructure::storage::FileSystemStorage;
|
||||
|
||||
pub struct CliInterface {
|
||||
mnemonic_generator: Bip39MnemonicGenerator,
|
||||
key_deriver: Ed25519KeyDeriver,
|
||||
file_encryptor: XChaCha20FileEncryptor,
|
||||
file_storage: FileSystemStorage,
|
||||
}
|
||||
|
||||
impl CliInterface {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
mnemonic_generator: Bip39MnemonicGenerator,
|
||||
key_deriver: Ed25519KeyDeriver,
|
||||
file_encryptor: XChaCha20FileEncryptor,
|
||||
file_storage: FileSystemStorage,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prompt_password(&self) -> Result<String, ApplicationError> {
|
||||
print!("Enter Access Password: ");
|
||||
io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let password = rpassword::read_password()
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
|
||||
|
||||
print!("Confirm Access Password: ");
|
||||
io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let password_confirm = rpassword::read_password()
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
|
||||
|
||||
if password != password_confirm {
|
||||
return Err(ApplicationError::InvalidInput("Passwords do not match".to_string()));
|
||||
}
|
||||
|
||||
if password.is_empty() {
|
||||
return Err(ApplicationError::InvalidInput("Password cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
Ok(password)
|
||||
}
|
||||
|
||||
pub fn prompt_recovery_phrase(&self) -> Result<Vec<String>, ApplicationError> {
|
||||
println!("Enter your 24-word Recovery Phrase (one word per line):");
|
||||
|
||||
let mut words = Vec::new();
|
||||
for i in 1..=24 {
|
||||
print!("Word {}: ", i);
|
||||
io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let mut word = String::new();
|
||||
io::stdin().read_line(&mut word)
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read input: {}", e)))?;
|
||||
|
||||
let word = word.trim().to_string();
|
||||
if word.is_empty() {
|
||||
return Err(ApplicationError::InvalidInput(format!("Word {} cannot be empty", i)));
|
||||
}
|
||||
words.push(word);
|
||||
}
|
||||
|
||||
Ok(words)
|
||||
}
|
||||
|
||||
pub fn handle_create(&self, output_path: &str) -> Result<(), ApplicationError> {
|
||||
let password = self.prompt_password()?;
|
||||
|
||||
let use_case = CreatePassportUseCase::new(
|
||||
self.mnemonic_generator.clone(),
|
||||
self.key_deriver.clone(),
|
||||
self.file_encryptor.clone(),
|
||||
self.file_storage.clone(),
|
||||
);
|
||||
|
||||
let (passport, recovery_phrase) = use_case.execute(&password, output_path)?;
|
||||
|
||||
println!("\n✅ Passport created successfully!");
|
||||
println!("\n📄 Passport saved to: {}", output_path);
|
||||
println!("\n🔑 Your DID: {}", passport.did().as_str());
|
||||
println!("\n📝 Your 24-word Recovery Phrase:");
|
||||
println!("{}", recovery_phrase.to_string());
|
||||
println!("\n⚠️ IMPORTANT: Store this Recovery Phrase securely offline!");
|
||||
println!(" It can regenerate your identity if you lose access.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_import_recovery(&self, output_path: &str) -> Result<(), ApplicationError> {
|
||||
let recovery_words = self.prompt_recovery_phrase()?;
|
||||
let password = self.prompt_password()?;
|
||||
|
||||
let use_case = ImportFromRecoveryUseCase::new(
|
||||
self.mnemonic_generator.clone(),
|
||||
self.key_deriver.clone(),
|
||||
self.file_encryptor.clone(),
|
||||
self.file_storage.clone(),
|
||||
);
|
||||
|
||||
let passport = use_case.execute(&recovery_words, &password, output_path)?;
|
||||
|
||||
println!("\n✅ Passport imported successfully!");
|
||||
println!("\n📄 Passport saved to: {}", output_path);
|
||||
println!("\n🔑 Your DID: {}", passport.did().as_str());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_import_file(&self, input_path: &str, output_path: Option<&str>) -> Result<(), ApplicationError> {
|
||||
print!("Enter Access Password for {}: ", input_path);
|
||||
io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let password = rpassword::read_password()
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
|
||||
|
||||
let use_case = ImportFromFileUseCase::new(
|
||||
self.file_encryptor.clone(),
|
||||
self.file_storage.clone(),
|
||||
);
|
||||
|
||||
let passport = use_case.execute(input_path, &password, output_path)?;
|
||||
|
||||
println!("\n✅ Passport imported successfully!");
|
||||
println!("\n🔑 Your DID: {}", passport.did().as_str());
|
||||
|
||||
if let Some(output_path) = output_path {
|
||||
println!("\n📄 Re-encrypted passport saved to: {}", output_path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_export(&self, input_path: &str, output_path: &str) -> Result<(), ApplicationError> {
|
||||
print!("Enter Access Password for {}: ", input_path);
|
||||
io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let password = rpassword::read_password()
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
|
||||
|
||||
let new_password = self.prompt_password()?;
|
||||
|
||||
// Load the passport first
|
||||
let import_use_case = ImportFromFileUseCase::new(
|
||||
self.file_encryptor.clone(),
|
||||
self.file_storage.clone(),
|
||||
);
|
||||
|
||||
let passport = import_use_case.execute(input_path, &password, None)?;
|
||||
|
||||
// Export with new password
|
||||
let export_use_case = ExportPassportUseCase::new(
|
||||
self.file_encryptor.clone(),
|
||||
self.file_storage.clone(),
|
||||
);
|
||||
|
||||
export_use_case.execute(&passport, &new_password, output_path)?;
|
||||
|
||||
println!("\n✅ Passport exported successfully!");
|
||||
println!("\n📄 New passport saved to: {}", output_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_info(&self, file_path: &str) -> Result<(), ApplicationError> {
|
||||
print!("Enter Access Password for {}: ", file_path);
|
||||
io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let password = rpassword::read_password()
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
|
||||
|
||||
let use_case = ImportFromFileUseCase::new(
|
||||
self.file_encryptor.clone(),
|
||||
self.file_storage.clone(),
|
||||
);
|
||||
|
||||
let passport = use_case.execute(file_path, &password, None)?;
|
||||
|
||||
println!("\n📋 Passport Information:");
|
||||
println!("🔑 DID: {}", passport.did().as_str());
|
||||
println!("🔐 Public Key: {}", hex::encode(passport.public_key().0.clone()));
|
||||
println!("📄 Source: {}", file_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_sign(&self, file_path: &str, message: &str) -> Result<(), ApplicationError> {
|
||||
print!("Enter Access Password for {}: ", file_path);
|
||||
io::stdout().flush().map_err(|e| ApplicationError::UseCaseError(format!("Failed to flush stdout: {}", e)))?;
|
||||
|
||||
let password = rpassword::read_password()
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
|
||||
|
||||
let use_case = ImportFromFileUseCase::new(
|
||||
self.file_encryptor.clone(),
|
||||
self.file_storage.clone(),
|
||||
);
|
||||
|
||||
let passport = use_case.execute(file_path, &password, None)?;
|
||||
|
||||
// Sign the message using the SignCardUseCase
|
||||
let sign_use_case = SignCardUseCase::new();
|
||||
let signature = sign_use_case.execute(&passport, message)?;
|
||||
|
||||
println!("\n✅ Message signed successfully!");
|
||||
println!("📝 Message: {}", message);
|
||||
println!("🔐 Signature: {}", hex::encode(&signature));
|
||||
println!("🔑 Public Key: {}", hex::encode(passport.public_key().0.clone()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,12 @@ use thiserror::Error;
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DomainError {
|
||||
#[error("Cryptographic error: {0}")]
|
||||
CryptographicError(String),
|
||||
|
||||
#[error("Invalid mnemonic: {0}")]
|
||||
InvalidMnemonic(String),
|
||||
|
||||
#[error("Invalid file format: {0}")]
|
||||
InvalidFileFormat(String),
|
||||
|
||||
#[error("File operation failed: {0}")]
|
||||
FileOperationError(String),
|
||||
#[error("Cryptographic error: {0}")]
|
||||
CryptographicError(String),
|
||||
}
|
||||
|
|
@ -3,4 +3,4 @@ pub mod traits;
|
|||
pub mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
mod entities_test;
|
||||
pub mod entities_test;
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
|
||||
use crate::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
|
||||
#[test]
|
||||
fn test_bip39_generator_creates_valid_mnemonic() {
|
||||
|
|
@ -56,10 +54,9 @@ mod tests {
|
|||
#[test]
|
||||
fn test_file_encryptor_round_trip() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
let key_deriver = Ed25519KeyDeriver;
|
||||
|
||||
let seed = Seed::new(vec![1; 32]);
|
||||
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
|
||||
let seed = Seed::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
||||
let public_key = PublicKey(vec![1; 32]);
|
||||
let did = Did::new(&public_key);
|
||||
let password = "test-password";
|
||||
|
||||
2
src/infrastructure/mod.rs
Normal file
2
src/infrastructure/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod crypto;
|
||||
pub mod storage;
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::NamedTempFile;
|
||||
use crate::domain::traits::FileStorage;
|
||||
use crate::{FileSystemStorage, PassportFile};
|
||||
|
||||
#[test]
|
||||
fn test_file_storage_round_trip() {
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
mod application;
|
||||
mod cli;
|
||||
mod domain;
|
||||
mod infrastructure;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::cli::commands::{Cli, Commands};
|
||||
use crate::cli::interface::CliInterface;
|
||||
use crate::application::error::ApplicationError;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn main() -> Result<(), ApplicationError> {
|
||||
let cli = Cli::parse();
|
||||
let interface = CliInterface::new();
|
||||
|
||||
Loading…
Add table
Reference in a new issue