Support GnuPG AEAD password entries

This commit is contained in:
Hermes Agent
2026-08-10 08:57:57 +00:00
parent d66d9b0f08
commit 977eb236da
10 changed files with 319 additions and 13 deletions

View File

@@ -504,6 +504,69 @@ fn missing_openpgp_passphrases_are_prompted_verified_and_only_then_persisted() -
Ok(())
}
#[test]
fn gnupg_aead_passphrases_are_persisted_only_after_authenticated_decryption() -> TestResult {
let fixture = FixtureSet::load()?;
let key = fixture.key("alice")?;
let keys = KeyStore::load(fixture.path(&key.secret_armor))?;
let ciphertext = fixture.read(&fixture.gnupg_aead.entry)?;
let expected = fixture.read(&fixture.gnupg_aead.plaintext)?;
let reference = SecretReference::openpgp_passphrase(&key.primary_fingerprint)?;
let prompt = MemoryPrompt::responding(Ok(key.passphrase.as_bytes()));
let mut provisioned =
store(MemoryBackend::default()).with_openpgp_passphrase_prompt(prompt.clone());
provisioned.unlock()?;
assert_eq!(
keys.decrypt(&EncryptedEntry::new(ciphertext.clone()), &mut provisioned)?
.expose(),
expected
);
assert_eq!(
provisioned.retrieve(&reference)?.expose(),
key.passphrase.as_bytes()
);
assert_eq!(
prompt.requested(),
std::slice::from_ref(&key.primary_fingerprint)
);
let wrong_prompt = MemoryPrompt::responding(Ok(b"incorrect AEAD passphrase"));
let mut wrong =
store(MemoryBackend::default()).with_openpgp_passphrase_prompt(wrong_prompt.clone());
wrong.unlock()?;
assert!(matches!(
keys.decrypt(&EncryptedEntry::new(ciphertext.clone()), &mut wrong),
Err(CryptoError::DecryptionFailed)
));
assert!(matches!(
wrong.retrieve(&reference),
Err(SecretStoreError::Missing)
));
assert_eq!(
wrong_prompt.requested(),
std::slice::from_ref(&key.primary_fingerprint)
);
let mut tampered_ciphertext = ciphertext;
*tampered_ciphertext
.last_mut()
.expect("non-empty AEAD fixture") ^= 0x01;
let tampered_prompt = MemoryPrompt::responding(Ok(key.passphrase.as_bytes()));
let mut tampered =
store(MemoryBackend::default()).with_openpgp_passphrase_prompt(tampered_prompt);
tampered.unlock()?;
assert!(matches!(
keys.decrypt(&EncryptedEntry::new(tampered_ciphertext), &mut tampered),
Err(CryptoError::DecryptionFailed)
));
assert!(matches!(
tampered.retrieve(&reference),
Err(SecretStoreError::Missing)
));
Ok(())
}
fn fs_config(temporary: &tempfile::TempDir) -> TestResult {
std::fs::create_dir_all(temporary.path().join("keys"))?;
std::fs::create_dir_all(temporary.path().join("vault"))?;