feat(notsecrets): EncryptedIdentity stub — passphrase-protected identity file

This commit is contained in:
Joseph O'Brien
2026-04-01 20:57:22 -04:00
parent a0fe6f7128
commit 545a45b3d3
3 changed files with 42 additions and 1 deletions

View File

@@ -0,0 +1,38 @@
use crate::error::AgeError;
use crate::identities::{FileKey, Identity, Stanza};
/// An identity whose key material is stored in an age-encrypted file.
///
/// The outer file is decrypted on each call to `unwrap_file_key` using
/// `passphrase_identity`. The decrypted content must be an `AGE-SECRET-KEY-1...`
/// bech32 string, which is parsed as an `X25519Identity`.
pub struct EncryptedIdentity {
/// Raw bytes of the age-encrypted identity file.
encrypted_data: Vec<u8>,
/// Identity used to decrypt the outer age file (typically `ScryptIdentity`).
passphrase_identity: Box<dyn Identity>,
}
impl EncryptedIdentity {
pub fn new(encrypted_data: Vec<u8>, passphrase_identity: Box<dyn Identity>) -> Self {
Self {
encrypted_data,
passphrase_identity,
}
}
}
impl Identity for EncryptedIdentity {
fn unwrap_file_key(&self, _stanza: &Stanza) -> Option<Result<FileKey, AgeError>> {
// Full implementation in Task 9 after Decryptor is available.
// The integration test below is marked #[ignore] until then.
let _ = &self.encrypted_data;
let _ = self.passphrase_identity.as_ref();
Some(Err(AgeError::CryptoError(
"EncryptedIdentity requires Decryptor — implemented in Task 9".to_string(),
)))
}
}
// Integration test for EncryptedIdentity lives in tests/integration.rs and is
// activated in Task 9 once Encryptor and Decryptor are available.

View File

@@ -12,6 +12,9 @@ pub use scrypt::ScryptIdentity;
pub mod ssh_rsa;
pub use ssh_rsa::SshRsaIdentity;
pub mod encrypted;
pub use encrypted::EncryptedIdentity;
/// The symmetric file encryption key — 16 random bytes.
#[derive(Clone, Debug)]
pub struct FileKey([u8; 16]);