use crate::TrustedLoadIntent;
use crate::events::normalizer::NativeKey;
use std::sync::Mutex;
pub(super) fn source_change_requires_reproof(
is_new_document: bool,
preceding_document_is_still_bound: bool,
) -> bool {
is_new_document && preceding_document_is_still_bound
}
enum TrustedLoadCorrelation {
Pending {
intent: TrustedLoadIntent,
expected_url: String,
},
Attested {
intent: TrustedLoadIntent,
expected_url: String,
navigation_key: NativeKey,
},
}
impl TrustedLoadCorrelation {
fn intent(&self) -> TrustedLoadIntent {
match self {
Self::Pending { intent, .. } | Self::Attested { intent, .. } => *intent,
}
}
}
#[derive(Clone, Copy)]
pub(super) enum TrustedNavigationStart {
Attest {
intent: TrustedLoadIntent,
navigation_key: NativeKey,
},
Revoke(TrustedLoadIntent),
Untrusted,
}
fn trusted_navigation_urls_match(expected: &str, actual: &str) -> bool {
fn canonical(url: &str) -> String {
let without_fragment = url.split_once('#').map(|(head, _)| head).unwrap_or(url);
super::scheme::normalize_memory_page_url(without_fragment).to_ascii_lowercase()
}
canonical(expected) == canonical(actual)
}
#[derive(Default)]
pub(super) struct WindowsDocumentAuthority {
correlation: Mutex<Option<TrustedLoadCorrelation>>,
}
impl WindowsDocumentAuthority {
pub(super) fn arm(
&self,
intent: TrustedLoadIntent,
expected_url: String,
) -> Option<TrustedLoadIntent> {
self.correlation
.lock()
.unwrap_or_else(|error| error.into_inner())
.replace(TrustedLoadCorrelation::Pending {
intent,
expected_url,
})
.map(|correlation| correlation.intent())
}
pub(super) fn navigation_start(
&self,
url: &str,
navigation_key: NativeKey,
) -> TrustedNavigationStart {
let mut correlation = self
.correlation
.lock()
.unwrap_or_else(|error| error.into_inner());
match correlation.take() {
Some(TrustedLoadCorrelation::Pending {
intent,
expected_url,
}) if navigation_key != 0 && trusted_navigation_urls_match(&expected_url, url) => {
*correlation = Some(TrustedLoadCorrelation::Attested {
intent,
expected_url,
navigation_key,
});
TrustedNavigationStart::Attest {
intent,
navigation_key,
}
}
Some(TrustedLoadCorrelation::Attested {
intent,
expected_url,
navigation_key: attested_key,
}) if attested_key == navigation_key
&& trusted_navigation_urls_match(&expected_url, url) =>
{
*correlation = Some(TrustedLoadCorrelation::Attested {
intent,
expected_url,
navigation_key,
});
TrustedNavigationStart::Untrusted
}
Some(correlation) => TrustedNavigationStart::Revoke(correlation.intent()),
None => TrustedNavigationStart::Untrusted,
}
}
pub(super) fn navigation_finished(&self, navigation_key: NativeKey) {
let mut correlation = self
.correlation
.lock()
.unwrap_or_else(|error| error.into_inner());
if matches!(
correlation.as_ref(),
Some(TrustedLoadCorrelation::Attested {
navigation_key: current,
..
}) if *current == navigation_key
) {
*correlation = None;
}
}
pub(super) fn revoke_if_matches(&self, intent: TrustedLoadIntent) -> bool {
let mut correlation = self
.correlation
.lock()
.unwrap_or_else(|error| error.into_inner());
if correlation
.as_ref()
.is_some_and(|correlation| correlation.intent() == intent)
{
*correlation = None;
true
} else {
false
}
}
pub(super) fn revoke_pending(&self) -> Option<TrustedLoadIntent> {
self.correlation
.lock()
.unwrap_or_else(|error| error.into_inner())
.take()
.map(|correlation| correlation.intent())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn intent(raw: u64) -> TrustedLoadIntent {
TrustedLoadIntent::new(raw)
}
#[test]
fn trailing_slash_and_scheme_case_still_attest_the_host_issued_load() {
let authority = WindowsDocumentAuthority::default();
assert!(
authority
.arm(intent(1), "lingxia://settings".into())
.is_none()
);
assert!(matches!(
authority.navigation_start("lingxia://settings/", 41),
TrustedNavigationStart::Attest {
intent: bound,
navigation_key: 41,
} if bound == intent(1)
));
let authority = WindowsDocumentAuthority::default();
authority.arm(intent(2), "lingxia://settings/".into());
assert!(matches!(
authority.navigation_start("Lingxia://Settings", 42),
TrustedNavigationStart::Attest { intent: bound, .. } if bound == intent(2)
));
}
#[test]
fn host_load_binds_only_the_first_matching_top_level_navigation() {
let authority = WindowsDocumentAuthority::default();
assert!(
authority
.arm(intent(1), "lingxia://settings".into())
.is_none()
);
assert!(matches!(
authority.navigation_start("lingxia://settings", 41),
TrustedNavigationStart::Attest {
intent: bound,
navigation_key: 41,
} if bound == intent(1)
));
assert!(matches!(
authority.navigation_start("lingxia://settings", 41),
TrustedNavigationStart::Untrusted
));
authority.navigation_finished(41);
assert!(authority.revoke_pending().is_none());
}
#[test]
fn redirect_to_an_unexpected_url_revokes_the_attested_intent() {
let authority = WindowsDocumentAuthority::default();
authority.arm(intent(6), "lingxia://settings".into());
assert!(matches!(
authority.navigation_start("lingxia://settings", 43),
TrustedNavigationStart::Attest { .. }
));
assert!(matches!(
authority.navigation_start("https://example.test/redirect", 43),
TrustedNavigationStart::Revoke(revoked) if revoked == intent(6)
));
assert!(authority.revoke_pending().is_none());
}
#[test]
fn external_or_keyless_navigation_revokes_the_pending_intent() {
let authority = WindowsDocumentAuthority::default();
authority.arm(intent(2), "lingxia://settings".into());
assert!(matches!(
authority.navigation_start("https://example.test/", 42),
TrustedNavigationStart::Revoke(revoked) if revoked == intent(2)
));
authority.arm(intent(3), "lingxia://settings".into());
assert!(matches!(
authority.navigation_start("lingxia://settings", 0),
TrustedNavigationStart::Revoke(revoked) if revoked == intent(3)
));
}
#[test]
fn replacement_and_crash_revoke_only_the_current_pending_load() {
let authority = WindowsDocumentAuthority::default();
authority.arm(intent(4), "lingxia://settings".into());
assert!(authority.arm(intent(5), "lingxia://downloads".into()) == Some(intent(4)));
assert!(!authority.revoke_if_matches(intent(4)));
assert!(authority.revoke_pending() == Some(intent(5)));
assert!(authority.revoke_pending().is_none());
}
#[test]
fn new_document_without_navigation_evidence_requires_reproof() {
assert!(source_change_requires_reproof(true, true));
}
#[test]
fn navigation_start_invalidates_before_new_document_source_change() {
assert!(!source_change_requires_reproof(true, false));
}
#[test]
fn same_document_history_changes_never_revoke_authority() {
assert!(!source_change_requires_reproof(false, true));
assert!(!source_change_requires_reproof(false, false));
}
}