refactor(vault): remove derive_password and site_password_path (ADR-025)
Drop the password-manager pattern from alknet-vault (drift item #7, ADR-025, resolves review #002 C9). Site-specific password derivation is not relevant to an RPC system's vault. Removed: - derive_password method from VaultServiceHandle (service.rs) - derive_password_string method from VaultServiceHandle (service.rs) - site_password_path function from derivation.rs - site-password derivation path row from derivation.rs doc table - All password-derivation tests from service.rs and derivation.rs - Now-unused base64 URL_SAFE_NO_PAD import from service.rs
This commit is contained in:
@@ -11,7 +11,6 @@
|
|||||||
//! | `m/74'/0'/0'/0'` | Primary identity keypair | Ed25519 (alknet auth) |
|
//! | `m/74'/0'/0'/0'` | Primary identity keypair | Ed25519 (alknet auth) |
|
||||||
//! | `m/74'/0'/0'/{n}'` | Worker/device identity | Ed25519 |
|
//! | `m/74'/0'/0'/{n}'` | Worker/device identity | Ed25519 |
|
||||||
//! | `m/74'/0'/1'/0'` | SSH host key | Ed25519 |
|
//! | `m/74'/0'/1'/0'` | SSH host key | Ed25519 |
|
||||||
//! | `m/74'/1'/0'/{hash}'` | Site-specific password | Deterministic |
|
|
||||||
//! | `m/74'/2'/0'/0'` | Encryption key for external credentials | AES-256-GCM |
|
//! | `m/74'/2'/0'/0'` | Encryption key for external credentials | AES-256-GCM |
|
||||||
//! | `m/44'/60'/0'/0/0` | Ethereum signing key | secp256k1 |
|
//! | `m/44'/60'/0'/0/0` | Ethereum signing key | secp256k1 |
|
||||||
|
|
||||||
@@ -52,13 +51,6 @@ pub fn device_path(index: u32) -> String {
|
|||||||
format!("m/74'/0'/0'/{}'", index)
|
format!("m/74'/0'/0'/{}'", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a site-specific password derivation path with the given hash.
|
|
||||||
///
|
|
||||||
/// Path: `m/74'/1'/0'/{hash}'`
|
|
||||||
pub fn site_password_path(site_hash: &str) -> String {
|
|
||||||
format!("m/74'/1'/0'/{}'", site_hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A derived extended private key with its public key.
|
/// A derived extended private key with its public key.
|
||||||
///
|
///
|
||||||
/// Contains the private key bytes and public key bytes from
|
/// Contains the private key bytes and public key bytes from
|
||||||
@@ -248,11 +240,6 @@ mod tests {
|
|||||||
assert_eq!(device_path(1), "m/74'/0'/0'/1'");
|
assert_eq!(device_path(1), "m/74'/0'/0'/1'");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_site_password_path() {
|
|
||||||
assert_eq!(site_password_path("abc123"), "m/74'/1'/0'/abc123'");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_derive_master_key_from_seed() {
|
fn test_derive_master_key_from_seed() {
|
||||||
// Use a known 64-byte seed
|
// Use a known 64-byte seed
|
||||||
|
|||||||
@@ -42,9 +42,6 @@
|
|||||||
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
|
||||||
use base64::Engine;
|
|
||||||
|
|
||||||
use crate::cache::{CacheConfig, CachedKey, KeyCache};
|
use crate::cache::{CacheConfig, CachedKey, KeyCache};
|
||||||
use crate::derivation::{self, DerivationError, PATHS};
|
use crate::derivation::{self, DerivationError, PATHS};
|
||||||
use crate::encryption::{self, EncryptedData, EncryptionKey};
|
use crate::encryption::{self, EncryptedData, EncryptionKey};
|
||||||
@@ -283,29 +280,6 @@ impl VaultServiceHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derive_password(&self, path: &str, length: usize) -> Result<Vec<u8>, VaultServiceError> {
|
|
||||||
let inner = self.inner.read().unwrap();
|
|
||||||
if !inner.unlocked {
|
|
||||||
return Err(VaultServiceError::VaultLocked);
|
|
||||||
}
|
|
||||||
let seed = inner.seed.as_ref().ok_or(VaultServiceError::VaultLocked)?;
|
|
||||||
|
|
||||||
let key = derivation::derive_path_from_seed(seed.as_bytes(), path)?;
|
|
||||||
let private_key = key.private_key();
|
|
||||||
let truncated_len = length.min(private_key.len());
|
|
||||||
let result = private_key[..truncated_len].to_vec();
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn derive_password_string(
|
|
||||||
&self,
|
|
||||||
path: &str,
|
|
||||||
length: usize,
|
|
||||||
) -> Result<String, VaultServiceError> {
|
|
||||||
let bytes = self.derive_password(path, length)?;
|
|
||||||
Ok(URL_SAFE_NO_PAD.encode(&bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypt plaintext using the derived encryption key.
|
/// Encrypt plaintext using the derived encryption key.
|
||||||
///
|
///
|
||||||
/// Uses the key at path `m/74'/2'/0'/0'` (PATHS::ENCRYPTION) by default.
|
/// Uses the key at path `m/74'/2'/0'/0'` (PATHS::ENCRYPTION) by default.
|
||||||
@@ -463,76 +437,6 @@ mod tests {
|
|||||||
assert!(service.decrypt(&encrypted).is_err());
|
assert!(service.decrypt(&encrypted).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_derive_password_deterministic() {
|
|
||||||
let service = VaultServiceHandle::new();
|
|
||||||
service.unlock_new(24).unwrap();
|
|
||||||
|
|
||||||
let path = "m/74'/1'/0'/12345'";
|
|
||||||
let pw1 = service.derive_password(path, 16).unwrap();
|
|
||||||
let pw2 = service.derive_password(path, 16).unwrap();
|
|
||||||
assert_eq!(pw1, pw2, "derive_password must be deterministic");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_derive_password_different_paths() {
|
|
||||||
let service = VaultServiceHandle::new();
|
|
||||||
service.unlock_new(24).unwrap();
|
|
||||||
|
|
||||||
let pw_a = service.derive_password("m/74'/1'/0'/100'", 16).unwrap();
|
|
||||||
let pw_b = service.derive_password("m/74'/1'/0'/200'", 16).unwrap();
|
|
||||||
assert_ne!(
|
|
||||||
pw_a, pw_b,
|
|
||||||
"different paths must produce different passwords"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_derive_password_length_truncation() {
|
|
||||||
let service = VaultServiceHandle::new();
|
|
||||||
service.unlock_new(24).unwrap();
|
|
||||||
|
|
||||||
let path = "m/74'/1'/0'/999'";
|
|
||||||
let pw_full = service.derive_password(path, 32).unwrap();
|
|
||||||
let pw_short = service.derive_password(path, 16).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(pw_short.len(), 16);
|
|
||||||
assert_eq!(pw_full.len(), 32);
|
|
||||||
assert_eq!(
|
|
||||||
&pw_full[..16],
|
|
||||||
&pw_short[..],
|
|
||||||
"truncated bytes must match prefix of full key"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_derive_password_locked_error() {
|
|
||||||
let service = VaultServiceHandle::new();
|
|
||||||
let result = service.derive_password("m/74'/1'/0'/1'", 16);
|
|
||||||
assert!(matches!(result, Err(VaultServiceError::VaultLocked)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_derive_password_string_base64url() {
|
|
||||||
let service = VaultServiceHandle::new();
|
|
||||||
service.unlock_new(24).unwrap();
|
|
||||||
|
|
||||||
let path = "m/74'/1'/0'/42'";
|
|
||||||
let encoded = service.derive_password_string(path, 16).unwrap();
|
|
||||||
|
|
||||||
assert!(!encoded.contains('='), "Base64url must not contain padding");
|
|
||||||
assert!(
|
|
||||||
encoded
|
|
||||||
.chars()
|
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|
|
||||||
"Base64url must only contain URL-safe characters"
|
|
||||||
);
|
|
||||||
|
|
||||||
let raw_bytes = service.derive_password(path, 16).unwrap();
|
|
||||||
let decoded = URL_SAFE_NO_PAD.decode(&encoded).unwrap();
|
|
||||||
assert_eq!(raw_bytes, decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "secp256k1")]
|
#[cfg(feature = "secp256k1")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_derive_ethereum_key_bip32() {
|
fn test_derive_ethereum_key_bip32() {
|
||||||
|
|||||||
Reference in New Issue
Block a user