12 KiB
Sharenet Passport Creator Implementation Plan
Overview
A Rust command-line application for creating and managing Sharenet Passports - cryptographic identities for the Sharenet protocol. The tool generates secure identities that can be used to join nodes, sign Cards, and encrypt communications.
Core Components
1. Passport Structure
Based on Sharenet Spec Sections 11, 27:
- 24-word recovery phrase (BIP-39)
- Derived Ed25519 keypair for signing
- DID constructed from public key
- Encrypted export format (.spf files)
- Cross-platform compatibility
2. Cryptographic Requirements
Algorithms (Spec Section 15):
- Mnemonic: BIP-39 with 256-bit entropy
- Key Derivation: Ed25519 from seed
- File Encryption: XChaCha20-Poly1305
- Key Derivation: HKDF-SHA256
- Hashing: SHA-256
Rust Crates:
bip39- BIP-39 mnemonic generationed25519-dalek- Ed25519 signatureschacha20poly1305- XChaCha20-Poly1305 encryptionhkdf- HKDF key derivationsha2- SHA-256 hashingrand- CSPRNG for key generationserde_cbor- CBOR serializationclap- CLI argument parsing
3. File Format (.spf)
CBOR Structure (before encryption):
struct PassportFile {
enc_seed: Vec<u8>, // seed encrypted under KEK
kdf: String, // "HKDF-SHA256"
cipher: String, // "XChaCha20-Poly1305"
salt: Vec<u8>, // 32-byte salt for KEK derivation
nonce: Vec<u8>, // 24-byte nonce for encryption
public_key: Vec<u8>, // Ed25519 public key
did: String, // Generated DID
created_at: u64, // Creation timestamp
version: String, // File format version
}
Storage:
- File extension:
.spf(Sharenet Passport File) - Default location:
~/.sharenet/passports/ - Secure file permissions (600)
CLI Interface
Commands
-
create- Generate new Passportsharenet-passport create [--output FILE] [--security-level LEVEL] -
import-recovery- Import from recovery phrasesharenet-passport import-recovery [--output FILE] -
import-file- Import from .spf filesharenet-passport import-file <FILE> [--security-level LEVEL] -
export- Export Passport to .spf filesharenet-passport export <FILE> [--output FILE] -
info- Display Passport detailssharenet-passport info [FILE] -
sign- Sign a message (testing)sharenet-passport sign <FILE> <MESSAGE>
Security Levels
Users can choose their preferred security/convenience trade-off:
maximum- Password required for every operationsession- Password on app start, keys in memory until close (default)timeout=Xh- Password required every X hourskeychain- Password once, keys stored in system keychain (desktop only)
Implementation Phases
Phase 1: Core Cryptographic Library
- BIP-39 mnemonic generation and validation
- Ed25519 key derivation from seed
- XChaCha20-Poly1305 encryption/decryption
- HKDF key derivation
- CBOR serialization/deserialization
Phase 2: Passport Data Structures
- Passport struct with recovery phrase, keys, DID
- .spf file format implementation
- File I/O operations with error handling
- Memory zeroization for sensitive data
Phase 3: CLI Implementation
- Command parsing with clap
- Secure password input (no echo)
- File permission enforcement
- User-friendly output formatting
Phase 4: Security Features
- Multiple security level implementations
- System keychain integration (desktop)
- Session management
- Error handling and validation
Phase 5: Testing & Documentation
- Unit tests for cryptographic operations
- Integration tests for CLI workflows
- Security testing and audit
- User documentation and examples
Project Structure
sharenet-passport-cli/
├── src/
│ ├── main.rs # CLI entry point
│ ├── cli/
│ │ ├── mod.rs # Command definitions
│ │ ├── create.rs # Create command
│ │ ├── import.rs # Import commands
│ │ └── export.rs # Export command
│ ├── crypto/
│ │ ├── mod.rs # Cryptographic operations
│ │ ├── bip39.rs # BIP-39 implementation
│ │ ├── encryption.rs # File encryption
│ │ └── keys.rs # Key generation
│ ├── passport/
│ │ ├── mod.rs # Passport data structures
│ │ ├── file_format.rs # .spf file handling
│ │ └── did.rs # DID generation
│ ├── storage/
│ │ ├── mod.rs # Storage abstractions
│ │ ├── file_system.rs # File I/O
│ │ └── keychain.rs # System keychain (desktop)
│ └── error.rs # Error types
├── Cargo.toml
├── tests/
│ ├── unit/
│ └── integration/
└── docs/
└── implementation_plan.md
Security Considerations
- Zeroize sensitive memory after use
- Secure password input handling
- File permission enforcement
- Cryptographic randomness verification
- Recovery phrase validation
- Error handling without information leakage
Testing Strategy
Unit Tests:
- Mnemonic generation and validation
- Key derivation consistency
- Encryption/decryption round-trip
- File format serialization
Integration Tests:
- Full CLI workflow (create → export → import → sign)
- Cross-platform file handling
- Password recovery scenarios
Security Tests:
- Memory zeroization verification
- File permission validation
- Cryptographic randomness testing
.spf File Import and Usage in Applications
This section details how .spf files would be imported and used in various application types, beyond the scope of the Passport Creator CLI itself.
Web Applications
Security Limitations
- Cannot access system keychains or secure storage
- Limited to browser storage (IndexedDB, localStorage)
- Private keys stored in potentially extractable formats
Implementation Options
Option 1: Session-Based (Recommended)
// On app start
async function loadPassport(spfFile, password) {
const passport = await decryptSPF(spfFile, password);
// Keep private key in memory only
sessionStorage.setItem('passport_loaded', 'true');
return passport;
}
// On app close or browser refresh
function cleanup() {
// Clear keys from memory
sessionStorage.removeItem('passport_loaded');
}
Option 2: Encrypted Browser Storage
// With user consent and security warning
async function storePassport(spfFile, password, storagePassword) {
const passport = await decryptSPF(spfFile, password);
const encryptedKey = await encryptForStorage(
passport.privateKey,
storagePassword
);
localStorage.setItem('encrypted_private_key', encryptedKey);
}
// Requires storage password on each use
async function loadFromStorage(storagePassword) {
const encrypted = localStorage.getItem('encrypted_private_key');
return await decryptFromStorage(encrypted, storagePassword);
}
Option 3: Per-Operation Password
// Maximum security, maximum inconvenience
async function signMessage(spfFile, password, message) {
const passport = await decryptSPF(spfFile, password);
const signature = await passport.sign(message);
// Immediately clear from memory
return signature;
}
Web Security Trade-offs
- Session-based: Good balance, but keys lost on browser close
- Encrypted storage: Convenient but relies on user-chosen password strength
- Per-operation: Most secure but poor user experience
Native Mobile Applications
Android Implementation
Using Android Keystore
class PassportManager {
private val keyStore = KeyStore.getInstance("AndroidKeyStore")
fun importSPF(spfFile: File, password: String) {
// Decrypt .spf file
val passport = decryptSPF(spfFile, password)
// Generate new keypair in Android Keystore
val keyPair = generateKeyPairInKeystore("sharenet_passport")
// The private key never leaves secure hardware
// Future operations use Keystore signing
}
fun signData(data: ByteArray): ByteArray {
// Sign directly using Keystore
return keyStore.getKey("sharenet_passport", null).sign(data)
}
}
Alternative: Encrypted SharedPreferences
fun storeInEncryptedPrefs(passport: Passport, password: String) {
val encryptedPrefs = EncryptedSharedPreferences.create(
"sharenet_passport",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
)
// Store encrypted private key
encryptedPrefs.edit()
.putString("encrypted_private_key", encryptKey(passport.privateKey, password))
.apply()
}
iOS Implementation
Using iOS Keychain
class PassportManager {
func importSPF(spfFile: URL, password: String) throws {
// Decrypt .spf file
let passport = try decryptSPF(spfFile, password: password)
// Store private key in Keychain
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: "sharenet.passport.private",
kSecValueRef as String: passport.privateKey,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)
}
func signData(_ data: Data) throws -> Data {
// Retrieve from Keychain and sign
let privateKey = try retrievePrivateKey()
return try privateKey.sign(data: data)
}
}
Desktop Applications
Cross-Platform Secure Storage
Using system keychains:
- macOS: Keychain Services
- Linux: libsecret / GNOME Keyring
- Windows: Credential Manager
Implementation Pattern:
// After importing .spf file once
fn store_in_keychain(passport: &Passport, password: &str) -> Result<()> {
let encrypted_key = encrypt_for_storage(&passport.private_key, password)?;
#[cfg(target_os = "macos")]
keychain::macos::store("sharenet_passport", &encrypted_key)?;
#[cfg(target_os = "linux")]
keychain::linux::store("sharenet_passport", &encrypted_key)?;
#[cfg(target_os = "windows")]
keychain::windows::store("sharenet_passport", &encrypted_key)?;
Ok(())
}
Password Decryption Scheme
.spf File Decryption Process
- Read .spf file and parse CBOR structure
- Derive KEK from user password using HKDF:
KEK = HKDF-SHA256(salt, password, info="sharenet-passport-kek") - Decrypt seed using XChaCha20-Poly1305:
seed = XChaCha20-Poly1305-Decrypt(KEK, nonce, enc_seed) - Regenerate keys from seed using BIP-39 derivation
- Verify integrity by comparing generated public key with stored public key
Application-Specific Storage
After initial .spf decryption, applications can choose their storage strategy:
- Web: Keep in memory or encrypt with separate password for browser storage
- Mobile: Store in platform secure storage (Keystore/Keychain)
- Desktop: Store in system keychain or keep in memory
Security Considerations for Each Platform
- Web: Highest risk - recommend session-based or per-operation passwords
- Mobile: Good security through platform mechanisms
- Desktop: Excellent security through system keychains
This approach allows users to maintain the same identity across different application types while each platform implements appropriate security measures for its environment.