Show TOTP codes in password details (#75)
This commit is contained in:
@@ -9,6 +9,7 @@ use std::{
|
||||
|
||||
use ironstorage::{
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
||||
document::EntryDocumentService,
|
||||
mobile_totp::{
|
||||
MobileTotpDiscoveryPhase, MobileTotpError, MobileTotpOperation, MobileTotpService,
|
||||
MobileWatchSnapshotState,
|
||||
@@ -305,6 +306,72 @@ fn totp_cache_reuses_ciphertext_hashes_and_removes_deleted_entries() -> TestResu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decrypted_entry_details_reconcile_totp_cache_without_persisting_secrets() -> TestResult {
|
||||
let fixture = FixtureSet::load()?;
|
||||
let store = fixture.materialize_store("basic")?;
|
||||
let repository = Repository::open(store.path())?;
|
||||
let keys = KeyStore::load(fixture.path("keys"))?;
|
||||
let path = EntryPath::parse("otp/detail")?;
|
||||
write_plaintext(
|
||||
&repository,
|
||||
&keys,
|
||||
"otp/detail",
|
||||
b"password\notpauth://totp/Acme:detail@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=Acme&digits=8&period=30\n",
|
||||
)?;
|
||||
let cache_directory = tempfile::tempdir()?;
|
||||
let cache = cache_directory.path().join("totp-catalog.toml");
|
||||
let service = MobileTotpService::new(&repository, &keys);
|
||||
let mut secrets = FixtureSecrets::all(&fixture);
|
||||
let document =
|
||||
EntryDocumentService::new(&repository, &keys).open("otp/detail", &mut secrets)?;
|
||||
let ciphertext = repository.read_entry(&path)?;
|
||||
|
||||
let reconciliation =
|
||||
service.reconcile_document(&document, &ciphertext, Some(59), &BTreeSet::new(), &cache)?;
|
||||
let detail = reconciliation.detail().expect("valid TOTP detail");
|
||||
assert_eq!(detail.code().expose(), b"94287082");
|
||||
assert_eq!(detail.valid_until(), 60);
|
||||
assert!(
|
||||
service
|
||||
.cached_page(&BTreeSet::new(), &cache)
|
||||
.expect("detail created cache")
|
||||
.rows()
|
||||
.iter()
|
||||
.any(|row| row.path() == "otp/detail")
|
||||
);
|
||||
let encoded = fs::read_to_string(&cache)?;
|
||||
assert!(encoded.contains("otp/detail"));
|
||||
assert!(encoded.contains("is_totp = true"));
|
||||
for secret in ["otpauth://", "secret=", "detail@example.com", "password"] {
|
||||
assert!(!encoded.contains(secret), "cache leaked {secret}");
|
||||
}
|
||||
|
||||
write_plaintext(
|
||||
&repository,
|
||||
&keys,
|
||||
"otp/detail",
|
||||
b"password\nlogin: detail\n",
|
||||
)?;
|
||||
let mut secrets = FixtureSecrets::all(&fixture);
|
||||
let document =
|
||||
EntryDocumentService::new(&repository, &keys).open("otp/detail", &mut secrets)?;
|
||||
let ciphertext = repository.read_entry(&path)?;
|
||||
let reconciliation =
|
||||
service.reconcile_document(&document, &ciphertext, Some(59), &BTreeSet::new(), &cache)?;
|
||||
assert!(reconciliation.detail().is_none());
|
||||
assert!(
|
||||
service
|
||||
.cached_page(&BTreeSet::new(), &cache)
|
||||
.expect("detail updated cache")
|
||||
.rows()
|
||||
.iter()
|
||||
.all(|row| row.path() != "otp/detail")
|
||||
);
|
||||
assert!(fs::read_to_string(cache)?.contains("is_totp = false"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelled_discovery_checkpoints_completed_entries() -> TestResult {
|
||||
let fixture = FixtureSet::load()?;
|
||||
|
||||
Reference in New Issue
Block a user