Dorian 62d6c13764 Implement onboarding reset functionality and enhance backup features
- Added a new method to reset the onboarding state, allowing users to re-initiate the onboarding process.
- Integrated backup creation functionality, enabling users to create encrypted backups of their node identity.
- Updated API endpoints to handle onboarding reset and backup creation requests.
- Enhanced UI components to support the new onboarding reset and backup features, including error handling and user feedback.
- Introduced new dependencies for cryptographic operations and data encoding.
2026-03-02 08:34:13 +00:00

69 lines
2.2 KiB
Rust

//! Encrypted DID identity backup for onboarding.
//! Uses Argon2 for key derivation and ChaCha20-Poly1305 for encryption.
use anyhow::{Context, Result};
use argon2::Argon2;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use chacha20poly1305::aead::{Aead, KeyInit};
use rand::RngCore;
use std::path::Path;
use tokio::fs;
const BACKUP_VERSION: u32 = 1;
const SALT_LEN: usize = 16;
const NONCE_LEN: usize = 12;
const KEY_LEN: usize = 32;
/// Create an encrypted backup of the node identity key.
/// Returns JSON-serializable backup metadata + encrypted blob (base64).
pub async fn create_encrypted_backup(
identity_dir: &Path,
passphrase: &str,
did: &str,
pubkey_hex: &str,
) -> Result<serde_json::Value> {
let key_path = identity_dir.join("node_key");
let key_bytes = fs::read(&key_path)
.await
.context("Failed to read node key")?;
if key_bytes.len() != 32 {
anyhow::bail!("Invalid node key length");
}
let mut salt = [0u8; SALT_LEN];
let mut nonce = [0u8; NONCE_LEN];
rand::rngs::OsRng.fill_bytes(&mut salt);
rand::rngs::OsRng.fill_bytes(&mut nonce);
let argon2 = Argon2::default();
let mut key = [0u8; KEY_LEN];
argon2
.hash_password_into(passphrase.as_bytes(), &salt, &mut key)
.map_err(|e| anyhow::anyhow!("Argon2 key derivation failed: {}", e))?;
let cipher = chacha20poly1305::ChaCha20Poly1305::new_from_slice(&key)
.map_err(|e| anyhow::anyhow!("Cipher init: {}", e))?;
let ciphertext = cipher
.encrypt(
chacha20poly1305::aead::generic_array::GenericArray::from_slice(&nonce),
key_bytes.as_ref(),
)
.map_err(|e| anyhow::anyhow!("Encryption failed: {}", e))?;
let mut blob = Vec::with_capacity(SALT_LEN + NONCE_LEN + ciphertext.len());
blob.extend_from_slice(&salt);
blob.extend_from_slice(&nonce);
blob.extend_from_slice(&ciphertext);
let blob_b64 = BASE64.encode(&blob);
Ok(serde_json::json!({
"version": BACKUP_VERSION,
"did": did,
"pubkey": pubkey_hex,
"kid": format!("{}#key-1", did),
"encrypted": true,
"blob": blob_b64,
"timestamp": chrono::Utc::now().to_rfc3339(),
}))
}