use secure_device_trust::{DeviceTrustDecision, DeviceTrustOutcome, TrustTier};
use secure_identity::BoundUserSession;
use secure_network::MtlsClientIdentity;
use crate::action::Action;
use crate::decision::{Decision, DenyReason};
use crate::resource::ResourceRef;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DeviceTrustProfile {
Production,
Test,
}
#[derive(Clone)]
pub struct DeviceTrustContext {
decision: DeviceTrustDecision,
mtls: MtlsClientIdentity,
bound_user_session: Option<BoundUserSession>,
profile: DeviceTrustProfile,
revoked_session: bool,
}
impl std::fmt::Debug for DeviceTrustContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeviceTrustContext")
.field("outcome", &self.decision.outcome())
.field("tier", &self.decision.tier())
.field("reasons", &self.decision.reasons())
.field("mtls_serial", &"<redacted>")
.field("mtls_fingerprint", &"<redacted>")
.field("has_bound_user_session", &self.bound_user_session.is_some())
.field("profile", &self.profile)
.field("revoked_session", &self.revoked_session)
.finish()
}
}
impl DeviceTrustContext {
#[must_use]
pub fn new(decision: DeviceTrustDecision, mtls: MtlsClientIdentity) -> Self {
Self {
decision,
mtls,
bound_user_session: None,
profile: DeviceTrustProfile::Production,
revoked_session: false,
}
}
#[must_use]
pub fn with_bound_user_session(mut self, session: BoundUserSession) -> Self {
self.bound_user_session = Some(session);
self
}
#[must_use]
pub fn with_profile(mut self, profile: DeviceTrustProfile) -> Self {
self.profile = profile;
self
}
#[must_use]
pub fn with_revoked_session(mut self, revoked_session: bool) -> Self {
self.revoked_session = revoked_session;
self
}
#[must_use]
pub fn decision(&self) -> &DeviceTrustDecision {
&self.decision
}
#[must_use]
pub fn mtls(&self) -> &MtlsClientIdentity {
&self.mtls
}
#[must_use]
pub fn bound_user_session(&self) -> Option<&BoundUserSession> {
self.bound_user_session.as_ref()
}
#[must_use]
pub fn profile(&self) -> DeviceTrustProfile {
self.profile
}
#[must_use]
pub fn is_revoked_session(&self) -> bool {
self.revoked_session
}
}
#[derive(Clone, Debug)]
pub struct DeviceTrustRequirement {
minimum_tier: TrustTier,
test_profile_only: bool,
require_bound_user_session: bool,
}
impl DeviceTrustRequirement {
#[must_use]
pub fn hardware_backed() -> Self {
Self {
minimum_tier: TrustTier::HardwareBacked,
test_profile_only: false,
require_bound_user_session: true,
}
}
#[must_use]
pub fn software_bound_test_only() -> Self {
Self {
minimum_tier: TrustTier::SoftwareBound,
test_profile_only: true,
require_bound_user_session: true,
}
}
#[must_use]
pub fn software_bound() -> Self {
Self {
minimum_tier: TrustTier::SoftwareBound,
test_profile_only: false,
require_bound_user_session: true,
}
}
#[must_use]
pub fn minimum_tier(&self) -> TrustTier {
self.minimum_tier
}
#[must_use]
pub fn is_test_profile_only(&self) -> bool {
self.test_profile_only
}
#[must_use]
pub fn requires_bound_user_session(&self) -> bool {
self.require_bound_user_session
}
}
#[derive(Clone, Debug)]
pub struct DeviceTrustRoutePolicy {
action: Action,
resource: ResourceRef,
requirement: DeviceTrustRequirement,
}
impl DeviceTrustRoutePolicy {
#[must_use]
pub fn new(action: Action, resource: ResourceRef, requirement: DeviceTrustRequirement) -> Self {
Self {
action,
resource,
requirement,
}
}
#[must_use]
pub fn action(&self) -> &Action {
&self.action
}
#[must_use]
pub fn resource(&self) -> &ResourceRef {
&self.resource
}
#[must_use]
pub fn requirement(&self) -> &DeviceTrustRequirement {
&self.requirement
}
pub fn evaluate(&self, context: Option<&DeviceTrustContext>) -> Decision {
evaluate_requirement(context, &self.requirement)
}
}
fn evaluate_requirement(
context: Option<&DeviceTrustContext>,
requirement: &DeviceTrustRequirement,
) -> Decision {
let Some(context) = context else {
return deny(DenyReason::DeviceTrustRequired);
};
if !context.mtls.trusted_edge {
return deny(DenyReason::UntrustedDeviceMetadata);
}
if context.revoked_session || context.decision.outcome() == DeviceTrustOutcome::Denied {
return deny(DenyReason::DeviceTrustRevoked);
}
if requirement.require_bound_user_session {
let Some(session) = &context.bound_user_session else {
return deny(DenyReason::DeviceTrustRequired);
};
if !session.is_bound_to(&context.mtls) {
return deny(DenyReason::DeviceSessionBindingMismatch);
}
}
if requirement.test_profile_only && context.profile != DeviceTrustProfile::Test {
return deny(DenyReason::TestTrustProfileRequired);
}
if context.decision.tier() < requirement.minimum_tier {
return deny(DenyReason::DeviceTrustTierTooLow);
}
Decision::Allow {
obligations: vec![format!(
"device-trust:{}",
trust_tier_label(context.decision.tier())
)],
}
}
fn deny(reason: DenyReason) -> Decision {
Decision::Deny { reason }
}
fn trust_tier_label(tier: TrustTier) -> &'static str {
match tier {
TrustTier::None => "none",
TrustTier::SoftwareBound => "software-bound",
TrustTier::HardwareBacked => "hardware-backed",
}
}