Merge pull request 'Implement restructuring' (#2) from feature/1-restructure-the-project-so-the-core-passport-functionality-is-a-library-crate into main
Reviewed-on: http://git.sharenet.sh/devteam/sharenet_passport_creator/pulls/2
This commit is contained in:
commit
a38845ef0b
27 changed files with 1555 additions and 262 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -632,16 +632,13 @@ dependencies = [
|
||||||
name = "sharenet-passport"
|
name = "sharenet-passport"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
|
||||||
"bip39",
|
"bip39",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"clap",
|
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"hex",
|
"hex",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"rand",
|
"rand",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"rpassword",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_cbor",
|
"serde_cbor",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|
@ -650,6 +647,18 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharenet-passport-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"assert_matches",
|
||||||
|
"clap",
|
||||||
|
"hex",
|
||||||
|
"rpassword",
|
||||||
|
"sharenet-passport",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signature"
|
name = "signature"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
|
|
||||||
22
Cargo.toml
22
Cargo.toml
|
|
@ -1,9 +1,12 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "sharenet-passport"
|
members = [
|
||||||
version = "0.1.0"
|
"libs/sharenet-passport",
|
||||||
edition = "2021"
|
"sharenet-passport-cli"
|
||||||
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[dependencies]
|
[workspace.dependencies]
|
||||||
|
# Common dependencies shared across workspace members
|
||||||
bip39 = "2.1"
|
bip39 = "2.1"
|
||||||
ed25519-dalek = { version = "2.1", features = ["serde"] }
|
ed25519-dalek = { version = "2.1", features = ["serde"] }
|
||||||
chacha20poly1305 = "0.10"
|
chacha20poly1305 = "0.10"
|
||||||
|
|
@ -13,13 +16,8 @@ rand = "0.8"
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_cbor = "0.11"
|
serde_cbor = "0.11"
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
zeroize = { version = "1.7", features = ["zeroize_derive"] }
|
zeroize = { version = "1.7", features = ["zeroize_derive"] }
|
||||||
|
hex = "0.4"
|
||||||
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
rpassword = "7.2"
|
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
|
cargo build --release
|
||||||
|
|
||||||
# Run the CLI
|
# Run the CLI
|
||||||
./target/release/sharenet-passport --help
|
./target/release/sharenet-passport-cli-cli --help
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage Guide
|
## Usage Guide
|
||||||
|
|
@ -34,7 +34,7 @@ cargo build --release
|
||||||
Create a new Sharenet Passport with a secure recovery phrase:
|
Create a new Sharenet Passport with a secure recovery phrase:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./target/release/sharenet-passport create my-passport.spf
|
./target/release/sharenet-passport-cli create --output my-passport.spf
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll be prompted to:
|
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:
|
If you have a 24-word recovery phrase, you can import it to create a new .spf file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./target/release/sharenet-passport import-recovery recovered-passport.spf
|
./target/release/sharenet-passport-cli import-recovery --output recovered-passport.spf
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll be prompted to:
|
You'll be prompted to:
|
||||||
|
|
@ -67,10 +67,10 @@ Import an existing .spf file (useful for re-encryption or verification):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Import without re-encryption
|
# Import without re-encryption
|
||||||
./target/release/sharenet-passport import-file existing.spf
|
./target/release/sharenet-passport-cli import-file existing.spf
|
||||||
|
|
||||||
# Import and re-encrypt to new file
|
# Import and re-encrypt to new file
|
||||||
./target/release/sharenet-passport import-file existing.spf new-passport.spf
|
./target/release/sharenet-passport-cli import-file existing.spf --output new-passport.spf
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll be prompted for the access password of the existing file.
|
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:
|
Export a passport with a new access password:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./target/release/sharenet-passport export old-passport.spf new-passport.spf
|
./target/release/sharenet-passport-cli export old-passport.spf --output new-passport.spf
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll be prompted to:
|
You'll be prompted to:
|
||||||
|
|
@ -92,7 +92,7 @@ You'll be prompted to:
|
||||||
Display information about a .spf file:
|
Display information about a .spf file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./target/release/sharenet-passport info my-passport.spf
|
./target/release/sharenet-passport-cli info my-passport.spf
|
||||||
```
|
```
|
||||||
|
|
||||||
Shows:
|
Shows:
|
||||||
|
|
@ -105,7 +105,7 @@ Shows:
|
||||||
Sign a message using your passport's private key:
|
Sign a message using your passport's private key:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./target/release/sharenet-passport sign my-passport.spf "Hello, Sharenet!"
|
./target/release/sharenet-passport-cli sign my-passport.spf "Hello, Sharenet!"
|
||||||
```
|
```
|
||||||
|
|
||||||
Output includes:
|
Output includes:
|
||||||
|
|
@ -144,17 +144,30 @@ Your 24-word recovery phrase is the master key to your identity:
|
||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo test
|
# 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
|
||||||
```
|
```
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
sharenet_passport_creator/
|
||||||
├── domain/ # Core entities and traits
|
├── libs/
|
||||||
├── application/ # Use cases and business logic
|
│ └── sharenet-passport/ # Core library crate
|
||||||
├── infrastructure/ # Crypto and storage implementations
|
│ ├── src/
|
||||||
└── cli/ # Command-line interface
|
│ │ ├── 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
|
||||||
```
|
```
|
||||||
|
|
||||||
### Architecture
|
### Architecture
|
||||||
|
|
|
||||||
37
libs/sharenet-passport/Cargo.toml
Normal file
37
libs/sharenet-passport/Cargo.toml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
[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"
|
||||||
|
|
@ -5,10 +5,6 @@ pub enum ApplicationError {
|
||||||
#[error("Use case error: {0}")]
|
#[error("Use case error: {0}")]
|
||||||
UseCaseError(String),
|
UseCaseError(String),
|
||||||
|
|
||||||
#[error("Invalid input: {0}")]
|
|
||||||
InvalidInput(String),
|
|
||||||
|
|
||||||
|
|
||||||
#[error("Domain error: {0}")]
|
#[error("Domain error: {0}")]
|
||||||
DomainError(#[from] crate::domain::error::DomainError),
|
DomainError(#[from] crate::domain::error::DomainError),
|
||||||
}
|
}
|
||||||
|
|
@ -2,4 +2,4 @@ pub mod use_cases;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod use_cases_test;
|
mod use_cases_test;
|
||||||
|
|
@ -2,12 +2,15 @@ use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum DomainError {
|
pub enum DomainError {
|
||||||
|
#[error("Cryptographic error: {0}")]
|
||||||
|
CryptographicError(String),
|
||||||
|
|
||||||
#[error("Invalid mnemonic: {0}")]
|
#[error("Invalid mnemonic: {0}")]
|
||||||
InvalidMnemonic(String),
|
InvalidMnemonic(String),
|
||||||
|
|
||||||
#[error("Invalid file format: {0}")]
|
#[error("Invalid file format: {0}")]
|
||||||
InvalidFileFormat(String),
|
InvalidFileFormat(String),
|
||||||
|
|
||||||
#[error("Cryptographic error: {0}")]
|
#[error("File operation failed: {0}")]
|
||||||
CryptographicError(String),
|
FileOperationError(String),
|
||||||
}
|
}
|
||||||
|
|
@ -3,4 +3,4 @@ pub mod traits;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod entities_test;
|
mod entities_test;
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::domain::entities::*;
|
use crate::domain::entities::*;
|
||||||
|
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
|
||||||
|
use crate::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bip39_generator_creates_valid_mnemonic() {
|
fn test_bip39_generator_creates_valid_mnemonic() {
|
||||||
|
|
@ -54,9 +56,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_file_encryptor_round_trip() {
|
fn test_file_encryptor_round_trip() {
|
||||||
let encryptor = XChaCha20FileEncryptor;
|
let encryptor = XChaCha20FileEncryptor;
|
||||||
|
let key_deriver = Ed25519KeyDeriver;
|
||||||
|
|
||||||
let seed = Seed::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
let seed = Seed::new(vec![1; 32]);
|
||||||
let public_key = PublicKey(vec![1; 32]);
|
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
|
||||||
let did = Did::new(&public_key);
|
let did = Did::new(&public_key);
|
||||||
let password = "test-password";
|
let password = "test-password";
|
||||||
|
|
||||||
15
libs/sharenet-passport/src/infrastructure/mod.rs
Normal file
15
libs/sharenet-passport/src/infrastructure/mod.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#[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;
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
use crate::domain::traits::FileStorage;
|
||||||
|
use crate::{FileSystemStorage, PassportFile};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_file_storage_round_trip() {
|
fn test_file_storage_round_trip() {
|
||||||
29
libs/sharenet-passport/src/lib.rs
Normal file
29
libs/sharenet-passport/src/lib.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
//! 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
|
||||||
|
};
|
||||||
16
sharenet-passport-cli/Cargo.toml
Normal file
16
sharenet-passport-cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[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"
|
||||||
163
sharenet-passport-cli/src/cli/interface.rs
Normal file
163
sharenet-passport-cli/src/cli/interface.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
mod application;
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod domain;
|
|
||||||
mod infrastructure;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::cli::commands::{Cli, Commands};
|
use crate::cli::commands::{Cli, Commands};
|
||||||
use crate::cli::interface::CliInterface;
|
use crate::cli::interface::CliInterface;
|
||||||
use crate::application::error::ApplicationError;
|
|
||||||
|
|
||||||
fn main() -> Result<(), ApplicationError> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
let interface = CliInterface::new();
|
let interface = CliInterface::new();
|
||||||
|
|
||||||
1231
sharenet_spec.md
Normal file
1231
sharenet_spec.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,216 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod crypto;
|
|
||||||
pub mod storage;
|
|
||||||
Loading…
Add table
Reference in a new issue