118 lines
3.8 KiB
Rust
118 lines
3.8 KiB
Rust
#![forbid(unsafe_code)]
|
|
|
|
mod support;
|
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
use ironstorage::{
|
|
crypto::{KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
|
mobile_totp::{MobileTotpService, MobileWatchSnapshotState},
|
|
recipient::RecipientPolicyManager,
|
|
repository::{EntryPath, Repository, SecretBytes},
|
|
};
|
|
use support::compatibility::{FixtureSet, TestResult};
|
|
|
|
struct FixtureSecrets(BTreeMap<String, Vec<u8>>);
|
|
|
|
impl FixtureSecrets {
|
|
fn all(fixture: &FixtureSet) -> Self {
|
|
Self(
|
|
fixture
|
|
.generated
|
|
.keys
|
|
.iter()
|
|
.map(|key| {
|
|
(
|
|
key.primary_fingerprint.clone(),
|
|
key.passphrase.as_bytes().to_vec(),
|
|
)
|
|
})
|
|
.collect(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl SecretProvider for FixtureSecrets {
|
|
fn secret_for(&mut self, key: &KeyInfo) -> Result<SecretBytes, SecretProviderError> {
|
|
self.0
|
|
.get(key.fingerprint().as_str())
|
|
.cloned()
|
|
.map(SecretBytes::new)
|
|
.ok_or(SecretProviderError::Unavailable)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn mobile_totp_catalog_details_and_watch_selection_are_storage_owned() -> TestResult {
|
|
let fixture = FixtureSet::load()?;
|
|
let store = fixture.materialize_store("basic")?;
|
|
let repository = Repository::open(store.path())?;
|
|
let keys = KeyStore::load(fixture.path("keys"))?;
|
|
write_plaintext(
|
|
&repository,
|
|
&keys,
|
|
"otp/alice",
|
|
b"password\notpauth://totp/Acme:alice@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=Acme&digits=8&period=30\n",
|
|
)?;
|
|
write_plaintext(
|
|
&repository,
|
|
&keys,
|
|
"otp/counter",
|
|
b"otpauth://hotp/Counter?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&counter=0\n",
|
|
)?;
|
|
write_plaintext(&repository, &keys, "ordinary", b"password\nlogin: alice\n")?;
|
|
|
|
let service = MobileTotpService::new(&repository, &keys);
|
|
let mut secrets = FixtureSecrets::all(&fixture);
|
|
let page = service.page(&BTreeSet::new(), &mut secrets)?;
|
|
let alice = page
|
|
.rows()
|
|
.iter()
|
|
.find(|row| row.path() == "otp/alice")
|
|
.expect("inserted TOTP row");
|
|
assert_eq!(alice.issuer(), Some("Acme"));
|
|
assert_eq!(alice.account(), "alice@example.com");
|
|
assert!(!alice.shared_with_watch());
|
|
assert!(page.rows().iter().all(|row| row.path() != "otp/counter"));
|
|
assert_eq!(page.watch().state(), MobileWatchSnapshotState::Unavailable);
|
|
assert!(!format!("{page:?}").contains("94287082"));
|
|
|
|
let detail = service.detail("otp/alice", 59, &BTreeSet::new(), &mut secrets)?;
|
|
assert_eq!(detail.code().expose(), b"94287082");
|
|
assert_eq!(detail.valid_until(), 60);
|
|
assert_eq!(detail.period(), 30);
|
|
assert!(!format!("{detail:?}").contains("94287082"));
|
|
|
|
let selected = BTreeSet::from([EntryPath::parse("otp/alice")?]);
|
|
let page = service.page(&selected, &mut secrets)?;
|
|
assert!(
|
|
page.rows()
|
|
.iter()
|
|
.find(|row| row.path() == "otp/alice")
|
|
.expect("selected TOTP row")
|
|
.shared_with_watch()
|
|
);
|
|
assert_eq!(page.watch().state(), MobileWatchSnapshotState::Pending);
|
|
let detail = service.detail("otp/alice", 60, &selected, &mut secrets)?;
|
|
assert!(detail.shared_with_watch());
|
|
assert_eq!(detail.valid_until(), 90);
|
|
Ok(())
|
|
}
|
|
|
|
fn write_plaintext(
|
|
repository: &Repository,
|
|
keys: &KeyStore,
|
|
path: &str,
|
|
plaintext: &[u8],
|
|
) -> TestResult {
|
|
let path = EntryPath::parse(path)?;
|
|
let recipients =
|
|
RecipientPolicyManager::new(repository, keys).resolve_for_entry(&path, None)?;
|
|
let encrypted = keys.encrypt(
|
|
SecretBytes::new(plaintext.to_vec()),
|
|
recipients.recipients(),
|
|
)?;
|
|
repository.write_entry(&path, &encrypted)?;
|
|
Ok(())
|
|
}
|