Present desktop OTP and sensitive outputs
This commit is contained in:
@@ -8,6 +8,7 @@ use data_encoding::BASE32_NOPAD;
|
||||
use ironstorage::{
|
||||
command::{OtpAppendRequest, OtpInputSource, OtpInsertRequest},
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
||||
desktop::{DesktopErrorKind, DesktopStorage},
|
||||
git::{GitIdentity, GitRepository},
|
||||
otp::{OtpAlgorithm, OtpCodeValidity, OtpError, OtpInput, OtpKind, OtpService, OtpUri},
|
||||
recipient::RecipientPolicyManager,
|
||||
@@ -513,6 +514,147 @@ fn automatic_code_supports_pass_diff_config_and_opens_git_only_for_hotp() -> Tes
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_otp_boundary_uses_rfc_codes_and_commits_import_replace_and_removal() -> 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 recipients = RecipientPolicyManager::new(&repository, &keys)
|
||||
.resolve_for_entry(&EntryPath::parse("desktop/rfc-totp")?, None)?;
|
||||
let secret = BASE32_NOPAD.encode(b"12345678901234567890");
|
||||
for (entry, plaintext) in [
|
||||
(
|
||||
"desktop/rfc-totp",
|
||||
format!(
|
||||
"password\notpauth://totp/RFC6238?secret={secret}&algorithm=SHA1&digits=8&period=30\n"
|
||||
),
|
||||
),
|
||||
(
|
||||
"desktop/rfc-hotp",
|
||||
format!("password\notpauth://hotp/RFC4226?secret={secret}&counter=0\n"),
|
||||
),
|
||||
] {
|
||||
repository.write_entry(
|
||||
&EntryPath::parse(entry)?,
|
||||
&keys.encrypt(
|
||||
SecretBytes::new(plaintext.into_bytes()),
|
||||
recipients.recipients(),
|
||||
)?,
|
||||
)?;
|
||||
}
|
||||
GitRepository::init(&repository, GitIdentity::ironstorage())?;
|
||||
let config_path = store.path().join("desktop-config.toml");
|
||||
fs::write(
|
||||
&config_path,
|
||||
format!(
|
||||
"vault = {:?}\ndefault_key = {:?}\nkey_material = {:?}\n",
|
||||
store.path(),
|
||||
fixture.generated.keys[0].primary_fingerprint,
|
||||
fixture.path("keys"),
|
||||
),
|
||||
)?;
|
||||
let desktop = DesktopStorage::load(Some(&config_path))?;
|
||||
let mut provider = FixtureSecrets::all(&fixture);
|
||||
|
||||
let totp = desktop.otp_code("desktop/rfc-totp", 59, false, &mut provider)?;
|
||||
assert_eq!(totp.code().expose(), b"94287082");
|
||||
assert_eq!(
|
||||
totp.validity(),
|
||||
OtpCodeValidity::Timed {
|
||||
valid_until: 60,
|
||||
period: 30,
|
||||
}
|
||||
);
|
||||
assert_eq!(totp.metadata().kind(), OtpKind::Totp);
|
||||
assert_eq!(totp.metadata().digits(), 8);
|
||||
|
||||
let hotp_path = EntryPath::parse("desktop/rfc-hotp")?;
|
||||
let before = repository.read_entry(&hotp_path)?;
|
||||
let denied = match desktop.otp_code("desktop/rfc-hotp", 59, false, &mut provider) {
|
||||
Ok(_) => panic!("HOTP requires confirmation"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(denied.kind(), DesktopErrorKind::Otp);
|
||||
assert_eq!(repository.read_entry(&hotp_path)?, before);
|
||||
let hotp = desktop.otp_code("desktop/rfc-hotp", 59, true, &mut provider)?;
|
||||
assert_eq!(hotp.code().expose(), b"287082");
|
||||
assert_eq!(
|
||||
hotp.validity(),
|
||||
OtpCodeValidity::CounterBased { counter: 1 }
|
||||
);
|
||||
let (_, _, _, tree, document) = hotp.into_parts();
|
||||
assert!(tree.is_some());
|
||||
assert_eq!(
|
||||
document
|
||||
.expect("HOTP returns refreshed document")
|
||||
.fields()
|
||||
.iter()
|
||||
.find_map(|field| field.metadata().otp())
|
||||
.and_then(|otp| otp.counter()),
|
||||
Some(1)
|
||||
);
|
||||
|
||||
let replacement = SecretBytes::new(
|
||||
b"otpauth://totp/Replaced:alice?secret=JBSWY3DPEHPK3PXP&issuer=Replaced".to_vec(),
|
||||
);
|
||||
let before = repository.read_entry(&EntryPath::parse("desktop/rfc-totp")?)?;
|
||||
assert!(
|
||||
desktop
|
||||
.import_otp(
|
||||
"desktop/rfc-totp",
|
||||
SecretBytes::new(replacement.expose().to_vec()),
|
||||
OverwriteDecision::Decline,
|
||||
&mut provider,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
repository.read_entry(&EntryPath::parse("desktop/rfc-totp")?)?,
|
||||
before
|
||||
);
|
||||
assert!(
|
||||
desktop
|
||||
.import_otp_qr(
|
||||
"desktop/rfc-totp",
|
||||
SecretBytes::new(b"not an image".to_vec()),
|
||||
OverwriteDecision::Allow,
|
||||
&mut provider,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
repository.read_entry(&EntryPath::parse("desktop/rfc-totp")?)?,
|
||||
before
|
||||
);
|
||||
let replaced = desktop.import_otp(
|
||||
"desktop/rfc-totp",
|
||||
replacement,
|
||||
OverwriteDecision::Allow,
|
||||
&mut provider,
|
||||
)?;
|
||||
assert_eq!(
|
||||
replaced
|
||||
.into_parts()
|
||||
.1
|
||||
.fields()
|
||||
.iter()
|
||||
.find_map(|field| field.metadata().otp())
|
||||
.and_then(|otp| otp.issuer()),
|
||||
Some("Replaced")
|
||||
);
|
||||
let removed = desktop.remove_otp("desktop/rfc-totp", &mut provider)?;
|
||||
assert!(
|
||||
removed
|
||||
.into_parts()
|
||||
.1
|
||||
.fields()
|
||||
.iter()
|
||||
.all(|field| field.metadata().kind() != ironstorage::document::EntryFieldKind::OtpUri)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_only_totp_ignores_git_that_mutations_correctly_reject() -> TestResult {
|
||||
let fixture = FixtureSet::load()?;
|
||||
|
||||
Reference in New Issue
Block a user