Implement the authenticated structured entry viewer
This commit is contained in:
@@ -7,7 +7,7 @@ use sha2::{Digest as _, Sha256};
|
||||
use crate::{
|
||||
command::EditRequest,
|
||||
crypto::{KeyStore, SecretProvider},
|
||||
otp::OtpUri,
|
||||
otp::{OtpAlgorithm, OtpKind, OtpUri},
|
||||
recipient::SigningPolicy,
|
||||
repository::{EntryPath, Repository, SecretBytes},
|
||||
write::{EditSession, EntryCommitter, VaultWriter, WriteError, WriteOutcome},
|
||||
@@ -46,6 +46,7 @@ pub struct EntryFieldMetadata {
|
||||
kind: EntryFieldKind,
|
||||
sensitivity: EntrySensitivity,
|
||||
name: Option<String>,
|
||||
otp: Option<EntryOtpMetadata>,
|
||||
value: Range<usize>,
|
||||
}
|
||||
|
||||
@@ -61,6 +62,52 @@ impl EntryFieldMetadata {
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
|
||||
pub fn otp(&self) -> Option<&EntryOtpMetadata> {
|
||||
self.otp.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-secret presentation metadata parsed from a validated `otpauth` URI.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct EntryOtpMetadata {
|
||||
kind: OtpKind,
|
||||
issuer: Option<String>,
|
||||
account: String,
|
||||
algorithm: OtpAlgorithm,
|
||||
digits: u32,
|
||||
period: Option<u64>,
|
||||
counter: Option<u64>,
|
||||
}
|
||||
|
||||
impl EntryOtpMetadata {
|
||||
pub fn kind(&self) -> OtpKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
pub fn issuer(&self) -> Option<&str> {
|
||||
self.issuer.as_deref()
|
||||
}
|
||||
|
||||
pub fn account(&self) -> &str {
|
||||
&self.account
|
||||
}
|
||||
|
||||
pub fn algorithm(&self) -> OtpAlgorithm {
|
||||
self.algorithm
|
||||
}
|
||||
|
||||
pub fn digits(&self) -> u32 {
|
||||
self.digits
|
||||
}
|
||||
|
||||
pub fn period(&self) -> Option<u64> {
|
||||
self.period
|
||||
}
|
||||
|
||||
pub fn counter(&self) -> Option<u64> {
|
||||
self.counter
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EntryField {
|
||||
@@ -205,6 +252,16 @@ impl EntryDocument {
|
||||
self.fields.first()
|
||||
}
|
||||
|
||||
/// Copy one structured field value into an independently zeroizing buffer.
|
||||
///
|
||||
/// Frontends use this for explicit presentation actions without reparsing
|
||||
/// the pass entry or copying label syntax alongside the selected value.
|
||||
pub fn copy_field_value(&self, id: EntryFieldId) -> Result<SecretBytes, DocumentError> {
|
||||
self.field(id)
|
||||
.map(|field| SecretBytes::new(field.value().to_vec()))
|
||||
.ok_or(DocumentError::UnknownField { id })
|
||||
}
|
||||
|
||||
pub fn conflict_token(&self) -> DocumentConflictToken {
|
||||
self.conflict_token
|
||||
}
|
||||
@@ -440,17 +497,30 @@ fn classify(index: usize, line: &[u8]) -> EntryFieldMetadata {
|
||||
kind: EntryFieldKind::Password,
|
||||
sensitivity: EntrySensitivity::Sensitive,
|
||||
name: Some("password".to_owned()),
|
||||
otp: None,
|
||||
value: 0..line.len(),
|
||||
};
|
||||
}
|
||||
if line.is_empty() {
|
||||
return blank_metadata();
|
||||
}
|
||||
if line.starts_with(b"otpauth://") && OtpUri::parse(SecretBytes::new(line.to_vec())).is_ok() {
|
||||
if line.starts_with(b"otpauth://")
|
||||
&& let Ok(uri) = OtpUri::parse(SecretBytes::new(line.to_vec()))
|
||||
{
|
||||
let otp = EntryOtpMetadata {
|
||||
kind: uri.kind(),
|
||||
issuer: uri.issuer().map(str::to_owned),
|
||||
account: uri.account().to_owned(),
|
||||
algorithm: uri.algorithm(),
|
||||
digits: uri.digits(),
|
||||
period: uri.period(),
|
||||
counter: uri.counter(),
|
||||
};
|
||||
return EntryFieldMetadata {
|
||||
kind: EntryFieldKind::OtpUri,
|
||||
sensitivity: EntrySensitivity::Sensitive,
|
||||
name: Some("otp".to_owned()),
|
||||
otp: Some(otp),
|
||||
value: 0..line.len(),
|
||||
};
|
||||
}
|
||||
@@ -465,6 +535,7 @@ fn classify(index: usize, line: &[u8]) -> EntryFieldMetadata {
|
||||
kind,
|
||||
sensitivity: field_sensitivity(name, kind),
|
||||
name: Some(name.to_owned()),
|
||||
otp: None,
|
||||
value: value_start..line.len(),
|
||||
};
|
||||
}
|
||||
@@ -473,6 +544,7 @@ fn classify(index: usize, line: &[u8]) -> EntryFieldMetadata {
|
||||
kind: EntryFieldKind::Note,
|
||||
sensitivity: EntrySensitivity::Sensitive,
|
||||
name: None,
|
||||
otp: None,
|
||||
value: 0..line.len(),
|
||||
}
|
||||
}
|
||||
@@ -504,6 +576,7 @@ fn blank_metadata() -> EntryFieldMetadata {
|
||||
kind: EntryFieldKind::Blank,
|
||||
sensitivity: EntrySensitivity::Empty,
|
||||
name: None,
|
||||
otp: None,
|
||||
value: 0..0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,19 @@ fn complex_documents_round_trip_with_storage_owned_metadata() -> TestResult {
|
||||
document.fields()[4].metadata().sensitivity(),
|
||||
EntrySensitivity::Sensitive
|
||||
);
|
||||
let otp = document.fields()[4]
|
||||
.metadata()
|
||||
.otp()
|
||||
.expect("validated OTP metadata");
|
||||
assert_eq!(otp.kind(), ironstorage::otp::OtpKind::Totp);
|
||||
assert_eq!(otp.issuer(), Some("Example"));
|
||||
assert_eq!(otp.account(), "alice");
|
||||
assert_eq!(otp.algorithm(), ironstorage::otp::OtpAlgorithm::Sha1);
|
||||
assert_eq!(otp.digits(), 6);
|
||||
assert_eq!(otp.period(), Some(30));
|
||||
assert_eq!(otp.counter(), None);
|
||||
let copied = document.copy_field_value(document.fields()[2].id())?;
|
||||
assert_eq!(copied.expose(), b"one");
|
||||
assert!(!format!("{document:?}").contains("pässwörd"));
|
||||
assert!(!format!("{:?}", document.fields()[4]).contains("JBSWY3DPEHPK3PXP"));
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user