feat(alknet-secret): make DerivedKey zeroize-on-drop, non-Clone, with redacted serialization

Per ADR-038, DerivedKey.private_key now derives Zeroize with #[zeroize(drop)]
ensuring sensitive key material is zeroized before deallocation. DerivedKey
is now move-only (no Clone), and JSON/debug output redacts private_key as
"[REDACTED]". Deserialization still works for postcard/irpc wire format.

Also fixes clippy needless_borrows_for_generic_args in encryption.rs and
applies cargo fmt to existing code.
This commit is contained in:
2026-06-10 06:16:38 +00:00
parent 8eb687afc0
commit eae47c366b
11 changed files with 220 additions and 40 deletions

View File

@@ -167,7 +167,10 @@ impl SecretServiceHandle {
if !inner.unlocked {
return Err(SecretServiceError::ServiceLocked);
}
let seed = inner.seed.as_ref().ok_or(SecretServiceError::ServiceLocked)?;
let seed = inner
.seed
.as_ref()
.ok_or(SecretServiceError::ServiceLocked)?;
let key = derivation::derive_path_from_seed(seed.as_bytes(), path)?;
Ok(DerivedKey {
@@ -183,7 +186,10 @@ impl SecretServiceHandle {
if !inner.unlocked {
return Err(SecretServiceError::ServiceLocked);
}
let seed = inner.seed.as_ref().ok_or(SecretServiceError::ServiceLocked)?;
let seed = inner
.seed
.as_ref()
.ok_or(SecretServiceError::ServiceLocked)?;
let key = derivation::derive_path_from_seed(seed.as_bytes(), path)?;
Ok(DerivedKey {
@@ -199,7 +205,10 @@ impl SecretServiceHandle {
if !inner.unlocked {
return Err(SecretServiceError::ServiceLocked);
}
let seed = inner.seed.as_ref().ok_or(SecretServiceError::ServiceLocked)?;
let seed = inner
.seed
.as_ref()
.ok_or(SecretServiceError::ServiceLocked)?;
let key = derivation::derive_path_from_seed(seed.as_bytes(), path)?;
Ok(DerivedKey {
@@ -212,12 +221,19 @@ impl SecretServiceHandle {
/// Encrypt plaintext using the derived encryption key.
///
/// Uses the key at path `m/74'/2'/0'/0'` (PATHS::ENCRYPTION) by default.
pub fn encrypt(&self, plaintext: &str, key_version: u32) -> Result<EncryptedData, SecretServiceError> {
pub fn encrypt(
&self,
plaintext: &str,
key_version: u32,
) -> Result<EncryptedData, SecretServiceError> {
let inner = self.inner.read().unwrap();
if !inner.unlocked {
return Err(SecretServiceError::ServiceLocked);
}
let seed = inner.seed.as_ref().ok_or(SecretServiceError::ServiceLocked)?;
let seed = inner
.seed
.as_ref()
.ok_or(SecretServiceError::ServiceLocked)?;
let derived = derivation::derive_path_from_seed(seed.as_bytes(), PATHS::ENCRYPTION)?;
let enc_key = EncryptionKey::from_derived_bytes(derived.private_key(), key_version);
@@ -231,10 +247,14 @@ impl SecretServiceHandle {
if !inner.unlocked {
return Err(SecretServiceError::ServiceLocked);
}
let seed = inner.seed.as_ref().ok_or(SecretServiceError::ServiceLocked)?;
let seed = inner
.seed
.as_ref()
.ok_or(SecretServiceError::ServiceLocked)?;
let derived = derivation::derive_path_from_seed(seed.as_bytes(), PATHS::ENCRYPTION)?;
let enc_key = EncryptionKey::from_derived_bytes(derived.private_key(), encrypted.key_version);
let enc_key =
EncryptionKey::from_derived_bytes(derived.private_key(), encrypted.key_version);
encryption::decrypt(encrypted, &enc_key).map_err(|e| e.into())
}
@@ -379,4 +399,4 @@ mod tests {
service.lock();
assert!(service.decrypt(&encrypted).is_err());
}
}
}