fix(auth): bind authoritative policy and binding evidence

Signed-off-by: Cea Stapleton Cordasco <261786559+cea-block@users.noreply.github.com>
This commit is contained in:
Cea Stapleton Cordasco
2026-08-03 15:31:57 -05:00
parent c3c17cf850
commit 8ea116a5d0
4 changed files with 759 additions and 36 deletions
+350 -27
View File
@@ -44,55 +44,196 @@ impl fmt::Debug for FederatedIdentityRequirement {
}
}
/// Exact authoritative enrollment-policy lineage for one decision.
///
/// This stamp is not provider capability-policy evidence. It names the
/// server-owned federated enrollment policy that supplied the requirement and
/// its half-open effective interval. The constructor validates shape, while the
/// O3 authority adapter remains responsible for sourcing current policy state.
#[derive(Clone, PartialEq, Eq)]
pub struct FederatedPolicyStamp {
authorization_domain: CommunityId,
policy_id: Uuid,
epoch: u64,
correlation_id: Uuid,
requirement: FederatedIdentityRequirement,
effective_from: u64,
effective_until: u64,
}
impl FederatedPolicyStamp {
/// Validate lineage read from current authoritative O3 policy state.
///
/// This constructor enforces structural invariants only. Callers must not
/// source any field from transport input, and O3 must compare the epoch as
/// an atomic precondition before enrollment.
pub fn from_authoritative_state(
authorization_domain: CommunityId,
policy_id: Uuid,
epoch: u64,
correlation_id: Uuid,
requirement: FederatedIdentityRequirement,
effective_from: u64,
effective_until: u64,
) -> Result<Self, AuthContextError> {
if policy_id.is_nil() {
return Err(AuthContextError::InvalidFederatedPolicyId);
}
if epoch == 0 {
return Err(AuthContextError::InvalidFederatedPolicyEpoch);
}
if correlation_id.is_nil() {
return Err(AuthContextError::InvalidFederatedPolicyCorrelation);
}
if effective_from >= effective_until {
return Err(AuthContextError::InvalidFederatedPolicyInterval);
}
Ok(Self {
authorization_domain,
policy_id,
epoch,
correlation_id,
requirement,
effective_from,
effective_until,
})
}
/// Authorization domain whose enrollment policy was resolved.
pub const fn authorization_domain(&self) -> CommunityId {
self.authorization_domain
}
/// Stable, non-nil identifier of the enrollment-policy namespace.
pub const fn policy_id(&self) -> Uuid {
self.policy_id
}
/// Positive monotonic epoch within the enrollment-policy namespace.
pub const fn epoch(&self) -> u64 {
self.epoch
}
/// Correlation identifier of the decision that resolved this policy.
pub const fn correlation_id(&self) -> Uuid {
self.correlation_id
}
/// Federated-identity requirement resolved at this epoch.
pub const fn requirement(&self) -> FederatedIdentityRequirement {
self.requirement
}
/// Inclusive start of the policy's effective interval.
pub const fn effective_from(&self) -> u64 {
self.effective_from
}
/// Exclusive end of the policy's effective interval.
pub const fn effective_until(&self) -> u64 {
self.effective_until
}
/// Whether the policy is not yet effective at trusted server time.
pub const fn is_not_yet_effective_at(&self, now_unix_seconds: u64) -> bool {
now_unix_seconds < self.effective_from
}
/// Whether the policy is expired at trusted server time.
pub const fn is_expired_at(&self, now_unix_seconds: u64) -> bool {
now_unix_seconds >= self.effective_until
}
}
impl fmt::Debug for FederatedPolicyStamp {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("FederatedPolicyStamp")
.field("authorization_domain", &"[redacted]")
.field("policy_id", &"[redacted]")
.field("epoch", &"[redacted]")
.field("correlation_id", &"[redacted]")
.field("requirement", &"[redacted]")
.field("effective_from", &"[redacted]")
.field("effective_until", &"[redacted]")
.finish()
}
}
/// Server-resolved federated-identity policy for an authorization decision.
///
/// Raw request data cannot construct this value. A policy adapter must resolve
/// the authorization domain's configuration before producing it. The evidence
/// is intentionally move-only and has no default or deserialization path.
/// A policy adapter must resolve the authorization domain's current
/// configuration before producing it; transport values are never authoritative
/// input. The evidence is intentionally move-only and has no default or
/// deserialization path.
#[derive(PartialEq, Eq)]
pub struct ResolvedFederatedPolicy {
authorization_domain: CommunityId,
requirement: FederatedIdentityRequirement,
stamp: FederatedPolicyStamp,
}
impl fmt::Debug for ResolvedFederatedPolicy {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ResolvedFederatedPolicy")
.field("authorization_domain", &"[redacted]")
.field("requirement", &"[redacted]")
.field("stamp", &self.stamp)
.finish()
}
}
impl ResolvedFederatedPolicy {
#[cfg(test)]
pub(crate) const fn not_required(authorization_domain: CommunityId) -> Self {
Self {
authorization_domain,
requirement: FederatedIdentityRequirement::NotRequired,
}
/// Seal structurally validated current O3 policy lineage for finalization.
pub const fn from_authoritative_resolution(stamp: FederatedPolicyStamp) -> Self {
Self { stamp }
}
#[cfg(test)]
pub(crate) const fn required(
pub(crate) fn not_required(authorization_domain: CommunityId) -> Self {
Self::from_authoritative_resolution(
FederatedPolicyStamp::from_authoritative_state(
authorization_domain,
Uuid::from_u128(40),
1,
Uuid::from_u128(2),
FederatedIdentityRequirement::NotRequired,
1,
u64::MAX,
)
.expect("synthetic federated policy lineage is valid"),
)
}
#[cfg(test)]
pub(crate) fn required(
authorization_domain: CommunityId,
enrollment_mode: EnrollmentMode,
) -> Self {
Self {
authorization_domain,
requirement: FederatedIdentityRequirement::Required(enrollment_mode),
}
Self::from_authoritative_resolution(
FederatedPolicyStamp::from_authoritative_state(
authorization_domain,
Uuid::from_u128(40),
1,
Uuid::from_u128(2),
FederatedIdentityRequirement::Required(enrollment_mode),
1,
u64::MAX,
)
.expect("synthetic federated policy lineage is valid"),
)
}
/// Authorization domain whose configuration was resolved.
pub const fn authorization_domain(&self) -> CommunityId {
self.authorization_domain
self.stamp.authorization_domain()
}
/// Resolved federated-identity requirement.
pub const fn requirement(&self) -> FederatedIdentityRequirement {
self.requirement
self.stamp.requirement()
}
/// Exact authoritative enrollment-policy lineage for this decision.
pub const fn stamp(&self) -> &FederatedPolicyStamp {
&self.stamp
}
}
@@ -191,13 +332,9 @@ impl BindingExpiry {
/// lease bound; expiry does not synthesize lifecycle state. An
/// authoritative binding adapter constructs this move-only value after checking
/// active lifecycle state; it has no default or deserialization path.
/// Production construction is intentionally unavailable in this phase. A
/// future crate-owned, sealed finalizer must require a durable non-nil ID, a
/// positive version, authoritative active-state evidence, and a typed database
/// result distinguishing a binding that already existed from one atomically
/// enrolled during this decision. Transport callers must never select that
/// lifecycle result. Pending, revoked, newly proposed, and synthetic records
/// must not cross that gate.
/// Production construction is available only through the crate-owned
/// authoritative-resolution finalizer. Pending, revoked, newly proposed, and
/// synthetic records must not cross that gate.
#[derive(PartialEq, Eq)]
pub struct VersionedBindingRef {
authorization_domain: CommunityId,
@@ -210,6 +347,144 @@ pub struct VersionedBindingRef {
resolution_reason: AuthorizationReason,
}
/// Structurally validated binding fields returned by authoritative O3 state.
///
/// This is not authorization by itself. The crate-owned finalizer additionally
/// requires a typed lifecycle outcome proving that the binding was already
/// active or was atomically enrolled during this decision. It has no default or
/// deserialization path.
#[derive(PartialEq, Eq)]
pub struct AuthoritativeBindingEvidence {
authorization_domain: CommunityId,
binding_id: Uuid,
principal: FederatedPrincipal,
bound_pubkey: PublicKey,
binding_version: BindingVersion,
expires_at: Option<BindingExpiry>,
source: BindingSource,
}
impl AuthoritativeBindingEvidence {
/// Validate typed fields read from authoritative binding state.
#[allow(clippy::too_many_arguments)]
pub fn new(
authorization_domain: CommunityId,
binding_id: Uuid,
principal: FederatedPrincipal,
bound_pubkey: PublicKey,
binding_version: BindingVersion,
expires_at: Option<BindingExpiry>,
source: BindingSource,
) -> Result<Self, AuthContextError> {
if binding_id.is_nil() {
return Err(AuthContextError::InvalidBindingId);
}
Ok(Self {
authorization_domain,
binding_id,
principal,
bound_pubkey,
binding_version,
expires_at,
source,
})
}
/// Server-resolved authorization domain that owns the binding.
pub const fn authorization_domain(&self) -> CommunityId {
self.authorization_domain
}
/// Stable binding identifier.
pub const fn binding_id(&self) -> Uuid {
self.binding_id
}
/// Issuer-qualified principal represented by the binding.
pub const fn principal(&self) -> &FederatedPrincipal {
&self.principal
}
/// Nostr key owned by the binding.
pub const fn bound_pubkey(&self) -> PublicKey {
self.bound_pubkey
}
/// Current local binding version.
pub const fn binding_version(&self) -> BindingVersion {
self.binding_version
}
/// Optional authoritative temporal bound for authorization eligibility.
pub const fn expires_at(&self) -> Option<BindingExpiry> {
self.expires_at
}
/// Persisted provenance of the active binding.
pub const fn source(&self) -> BindingSource {
self.source
}
}
impl fmt::Debug for AuthoritativeBindingEvidence {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AuthoritativeBindingEvidence")
.field("authorization_domain", &"[redacted]")
.field("binding_id", &"[redacted]")
.field("principal", &"[redacted]")
.field("bound_pubkey", &"[redacted]")
.field("binding_version", &"[redacted]")
.field("expires_at", &"[redacted]")
.field("source", &"[redacted]")
.finish()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum BindingResolutionOutcome {
ExistingActive,
AtomicallyEnrolled,
}
/// Typed authoritative lifecycle result consumed by the O1 finalizer.
///
/// It carries no caller-selected authorization reason; O1 derives that reason
/// from the lifecycle outcome, persisted provenance, and current enrollment
/// policy.
#[derive(PartialEq, Eq)]
pub struct AuthoritativeBindingResolution {
evidence: AuthoritativeBindingEvidence,
outcome: BindingResolutionOutcome,
}
impl AuthoritativeBindingResolution {
/// Record O3's authoritative result that the binding already existed.
pub fn existing_active(evidence: AuthoritativeBindingEvidence) -> Self {
Self {
evidence,
outcome: BindingResolutionOutcome::ExistingActive,
}
}
/// Record O3's authoritative result that enrollment committed atomically.
pub fn atomically_enrolled(evidence: AuthoritativeBindingEvidence) -> Self {
Self {
evidence,
outcome: BindingResolutionOutcome::AtomicallyEnrolled,
}
}
}
impl fmt::Debug for AuthoritativeBindingResolution {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("AuthoritativeBindingResolution")
.field(&"[redacted]")
.finish()
}
}
impl fmt::Debug for VersionedBindingRef {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
@@ -227,6 +502,54 @@ impl fmt::Debug for VersionedBindingRef {
}
impl VersionedBindingRef {
pub(super) fn from_authoritative_resolution(
resolution: AuthoritativeBindingResolution,
requirement: FederatedIdentityRequirement,
) -> Result<Self, AuthContextError> {
let reason = match resolution.outcome {
BindingResolutionOutcome::ExistingActive => AuthorizationReason::ExistingBinding,
BindingResolutionOutcome::AtomicallyEnrolled => {
match (requirement, resolution.evidence.source) {
(
FederatedIdentityRequirement::Required(EnrollmentMode::AttestedKey),
BindingSource::AttestedKey,
) => AuthorizationReason::EnrolledAttestedKey,
(
FederatedIdentityRequirement::Required(EnrollmentMode::Tofu),
BindingSource::Tofu | BindingSource::AttestedKey,
) => AuthorizationReason::EnrolledTofu,
_ => return Err(AuthContextError::InvalidAuthorizationReason),
}
}
};
Ok(Self::from_authoritative_evidence(
resolution.evidence,
reason,
))
}
pub(super) fn from_existing_authoritative_evidence(
evidence: AuthoritativeBindingEvidence,
) -> Self {
Self::from_authoritative_evidence(evidence, AuthorizationReason::ExistingBinding)
}
fn from_authoritative_evidence(
evidence: AuthoritativeBindingEvidence,
resolution_reason: AuthorizationReason,
) -> Self {
Self {
authorization_domain: evidence.authorization_domain,
binding_id: evidence.binding_id,
principal: evidence.principal,
bound_pubkey: evidence.bound_pubkey,
binding_version: evidence.binding_version,
expires_at: evidence.expires_at,
source: evidence.source,
resolution_reason,
}
}
/// Build a reference to a binding authoritatively resolved as already active.
#[cfg(test)]
pub(crate) fn new_existing_active_for_test(
+95 -5
View File
@@ -18,7 +18,8 @@ mod evidence;
mod reason;
pub use binding::{
BindingExpiry, BindingSource, BindingVersion, EnrollmentMode, FederatedIdentityRequirement,
AuthoritativeBindingEvidence, AuthoritativeBindingResolution, BindingExpiry, BindingSource,
BindingVersion, EnrollmentMode, FederatedIdentityRequirement, FederatedPolicyStamp,
ResolvedFederatedPolicy, VersionedBindingRef,
};
pub use evidence::{
@@ -72,6 +73,39 @@ impl fmt::Debug for FederatedAuthorization {
}
}
/// Authoritative O3 result consumed by the public O1 production finalizer.
///
/// Unlike [`FederatedAuthorization`], this input cannot contain a raw
/// [`VersionedBindingRef`] or a caller-selected authorization reason.
#[derive(PartialEq, Eq)]
pub enum AuthoritativeFederatedResolution {
/// This domain's current policy does not require federated identity.
NotRequired,
/// Direct authority backed by an existing or atomically enrolled binding.
Direct {
/// Typed authoritative O3 lifecycle result.
binding: AuthoritativeBindingResolution,
/// Current verified assertion for the authenticated actor.
assertion: VerifiedFederatedAssertion,
},
/// Delegated authority backed by an already-active owner binding.
Delegated {
/// Typed authoritative evidence for the existing owner binding.
owner: AuthoritativeBindingEvidence,
/// Current admission resolved for the owner.
admission: VerifiedOwnerAdmission,
},
}
impl fmt::Debug for AuthoritativeFederatedResolution {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("AuthoritativeFederatedResolution")
.field(&"[redacted]")
.finish()
}
}
/// Initial shared authorization-context contract.
#[derive(PartialEq, Eq)]
pub struct AuthContextV1 {
@@ -148,6 +182,41 @@ impl fmt::Debug for AuthContext {
}
impl AuthContext {
/// Finalize an immutable V1 context from authoritative O3 binding evidence.
///
/// This is the production O1↔O3 seam. O3 must resolve the enrollment-policy
/// stamp and binding lifecycle state from current authoritative storage,
/// use the policy epoch as a conditional precondition for any atomic
/// enrollment, and pass the resulting typed lifecycle outcome here. O1
/// derives the authorization reason; transport code cannot select it.
pub fn finalize_authoritative_v1(
input: AuthContextInput,
federated_policy: ResolvedFederatedPolicy,
resolution: AuthoritativeFederatedResolution,
now_unix_seconds: u64,
) -> Result<Self, AuthContextError> {
validate_federated_policy_stamp(&input, &federated_policy, now_unix_seconds)?;
let authorization = match resolution {
AuthoritativeFederatedResolution::NotRequired => FederatedAuthorization::NotRequired,
AuthoritativeFederatedResolution::Direct { binding, assertion } => {
FederatedAuthorization::Direct {
binding: VersionedBindingRef::from_authoritative_resolution(
binding,
federated_policy.requirement(),
)?,
assertion,
}
}
AuthoritativeFederatedResolution::Delegated { owner, admission } => {
FederatedAuthorization::Delegated {
owner: VersionedBindingRef::from_existing_authoritative_evidence(owner),
admission,
}
}
};
Self::finalize_v1(input, federated_policy, authorization, now_unix_seconds)
}
/// Validate all authorization evidence and finalize an immutable V1 context.
///
/// Adapters must preserve this phase order: cryptographic proof and
@@ -158,7 +227,7 @@ impl AuthContext {
///
/// `now_unix_seconds` must come from the server clock for the authorization
/// decision being finalized.
pub fn finalize_v1(
pub(crate) fn finalize_v1(
input: AuthContextInput,
federated_policy: ResolvedFederatedPolicy,
authorization: FederatedAuthorization,
@@ -169,9 +238,7 @@ impl AuthContext {
if input.nostr_proof.authorization_domain() != authorization_domain {
return Err(AuthContextError::NostrProofDomainMismatch);
}
if federated_policy.authorization_domain() != authorization_domain {
return Err(AuthContextError::PolicyDomainMismatch);
}
validate_federated_policy_stamp(&input, &federated_policy, now_unix_seconds)?;
if input.community_access.authorization_domain() != authorization_domain {
return Err(AuthContextError::CommunityAccessDomainMismatch);
}
@@ -293,6 +360,29 @@ impl AuthContext {
}
}
fn validate_federated_policy_stamp(
input: &AuthContextInput,
federated_policy: &ResolvedFederatedPolicy,
now_unix_seconds: u64,
) -> Result<(), AuthContextError> {
if federated_policy.authorization_domain() != input.tenant.community() {
return Err(AuthContextError::PolicyDomainMismatch);
}
if federated_policy.stamp().correlation_id() != input.correlation_id {
return Err(AuthContextError::FederatedPolicyCorrelationMismatch);
}
if federated_policy
.stamp()
.is_not_yet_effective_at(now_unix_seconds)
{
return Err(AuthContextError::FederatedPolicyNotYetEffective);
}
if federated_policy.stamp().is_expired_at(now_unix_seconds) {
return Err(AuthContextError::FederatedPolicyExpired);
}
Ok(())
}
pub(super) const fn transport_accepts_proof(
transport: AuthTransport,
proof_method: AuthMethod,
+28
View File
@@ -61,6 +61,18 @@ pub enum AuthContextError {
/// Binding expiry was not a valid Unix timestamp.
#[error("identity binding expiry must be greater than zero")]
InvalidBindingExpiry,
/// Enrollment-policy identifier was nil.
#[error("federated enrollment-policy identifier must not be nil")]
InvalidFederatedPolicyId,
/// Enrollment-policy epoch was zero.
#[error("federated enrollment-policy epoch must be greater than zero")]
InvalidFederatedPolicyEpoch,
/// Enrollment-policy correlation identifier was nil.
#[error("federated enrollment-policy correlation must not be nil")]
InvalidFederatedPolicyCorrelation,
/// Enrollment-policy effective interval was empty or reversed.
#[error("federated enrollment-policy effective interval is invalid")]
InvalidFederatedPolicyInterval,
/// Assertion expiry was not a valid Unix timestamp.
#[error("federated assertion expiry must be greater than zero")]
InvalidAssertionExpiry,
@@ -76,6 +88,15 @@ pub enum AuthContextError {
/// Binding was no longer authorization-eligible when evaluated.
#[error("identity binding has expired")]
BindingExpired,
/// Enrollment policy was resolved for another authorization decision.
#[error("federated enrollment policy does not match the authorization decision")]
FederatedPolicyCorrelationMismatch,
/// Enrollment policy was used before its effective interval.
#[error("federated enrollment policy is not yet effective")]
FederatedPolicyNotYetEffective,
/// Enrollment policy was used after its effective interval.
#[error("federated enrollment policy has expired")]
FederatedPolicyExpired,
/// Assertion was used before its validated not-before bound.
#[error("federated assertion is not yet valid")]
AssertionNotYetValid,
@@ -159,11 +180,18 @@ impl AuthContextError {
Self::InvalidBindingVersion => "federated_binding_invalid_version",
Self::InvalidBindingId => "federated_binding_invalid_id",
Self::InvalidBindingExpiry => "federated_binding_invalid_expiry",
Self::InvalidFederatedPolicyId => "federated_policy_invalid_id",
Self::InvalidFederatedPolicyEpoch => "federated_policy_invalid_epoch",
Self::InvalidFederatedPolicyCorrelation => "federated_policy_invalid_correlation",
Self::InvalidFederatedPolicyInterval => "federated_policy_invalid_interval",
Self::InvalidAssertionExpiry => "federated_assertion_invalid_expiry",
Self::InvalidDelegationExpiry => "delegation_invalid_expiry",
Self::InvalidAdmissionExpiry => "owner_admission_invalid_expiry",
Self::AssertionExpired => "federated_assertion_expired",
Self::BindingExpired => "federated_binding_expired",
Self::FederatedPolicyCorrelationMismatch => "federated_policy_correlation_mismatch",
Self::FederatedPolicyNotYetEffective => "federated_policy_not_yet_effective",
Self::FederatedPolicyExpired => "federated_policy_expired",
Self::AssertionNotYetValid => "federated_assertion_not_yet_valid",
Self::KeyAttestationRequired => "federated_key_attestation_required",
Self::KeyAttestationMismatch => "federated_key_attestation_mismatch",
+286 -4
View File
@@ -92,10 +92,46 @@ fn policy_required(enrollment_mode: EnrollmentMode) -> ResolvedFederatedPolicy {
ResolvedFederatedPolicy::required(authorization_domain(1), enrollment_mode)
}
fn policy_with_lineage(
enrollment_mode: EnrollmentMode,
correlation_id: Uuid,
effective_from: u64,
effective_until: u64,
) -> ResolvedFederatedPolicy {
ResolvedFederatedPolicy::from_authoritative_resolution(
FederatedPolicyStamp::from_authoritative_state(
authorization_domain(1),
Uuid::from_u128(40),
7,
correlation_id,
FederatedIdentityRequirement::Required(enrollment_mode),
effective_from,
effective_until,
)
.expect("synthetic federated policy lineage is valid"),
)
}
fn binding(pubkey: PublicKey) -> VersionedBindingRef {
binding_in(1, pubkey)
}
fn authoritative_binding_evidence(
pubkey: PublicKey,
source: BindingSource,
) -> AuthoritativeBindingEvidence {
AuthoritativeBindingEvidence::new(
authorization_domain(1),
Uuid::from_u128(10),
principal(),
pubkey,
BindingVersion::INITIAL,
None,
source,
)
.expect("synthetic authoritative binding evidence is valid")
}
fn binding_in(domain: u128, pubkey: PublicKey) -> VersionedBindingRef {
binding_with_source_in(domain, pubkey, BindingSource::AttestedKey)
}
@@ -395,7 +431,10 @@ fn context_debug_output_omits_tenant_host() {
"nostr: NostrAuthority { actor_pubkey: \"[redacted]\", ",
"proof_method: Nip42, verified_delegation: \"[redacted]\" }, ",
"federated_policy: ResolvedFederatedPolicy { ",
"authorization_domain: \"[redacted]\", requirement: \"[redacted]\" }, ",
"stamp: FederatedPolicyStamp { authorization_domain: \"[redacted]\", ",
"policy_id: \"[redacted]\", epoch: \"[redacted]\", ",
"correlation_id: \"[redacted]\", requirement: \"[redacted]\", ",
"effective_from: \"[redacted]\", effective_until: \"[redacted]\" } }, ",
"federated: FederatedAuthorization(\"[redacted]\"), ",
"scopes: \"[redacted]\", channel_ids: \"[redacted]\" })"
)
@@ -814,7 +853,12 @@ fn enrolled_reason_must_match_policy_and_binding_source() {
BindingSource::AttestedKey,
AuthorizationReason::EnrolledAttestedKey,
),
assertion: assertion(principal(), AssertionTransport::TrustedProxy, 200),
assertion: assertion_with_attested_key(
principal(),
AssertionTransport::TrustedProxy,
200,
actor.public_key(),
),
},
100,
)
@@ -1047,11 +1091,18 @@ fn authorization_error_codes_are_unique_and_provider_neutral() {
AuthContextError::InvalidBindingVersion,
AuthContextError::InvalidBindingId,
AuthContextError::InvalidBindingExpiry,
AuthContextError::InvalidFederatedPolicyId,
AuthContextError::InvalidFederatedPolicyEpoch,
AuthContextError::InvalidFederatedPolicyCorrelation,
AuthContextError::InvalidFederatedPolicyInterval,
AuthContextError::InvalidAssertionExpiry,
AuthContextError::InvalidDelegationExpiry,
AuthContextError::InvalidAdmissionExpiry,
AuthContextError::AssertionExpired,
AuthContextError::BindingExpired,
AuthContextError::FederatedPolicyCorrelationMismatch,
AuthContextError::FederatedPolicyNotYetEffective,
AuthContextError::FederatedPolicyExpired,
AuthContextError::AssertionNotYetValid,
AuthContextError::KeyAttestationRequired,
AuthContextError::KeyAttestationMismatch,
@@ -1146,12 +1197,243 @@ fn security_posture_debug_output_is_fully_redacted() {
assert_eq!(
format!("{:?}", policy_required(EnrollmentMode::AttestedKey)),
concat!(
"ResolvedFederatedPolicy { authorization_domain: \"[redacted]\", ",
"requirement: \"[redacted]\" }"
"ResolvedFederatedPolicy { stamp: FederatedPolicyStamp { ",
"authorization_domain: \"[redacted]\", policy_id: \"[redacted]\", ",
"epoch: \"[redacted]\", correlation_id: \"[redacted]\", ",
"requirement: \"[redacted]\", effective_from: \"[redacted]\", ",
"effective_until: \"[redacted]\" } }"
)
);
}
#[test]
fn federated_policy_must_match_the_authorization_correlation() {
let actor = Keys::generate();
let error = AuthContext::finalize_v1(
input(actor.public_key(), AuthTransport::RelayWebSocket, None),
policy_with_lineage(EnrollmentMode::AttestedKey, Uuid::from_u128(99), 1, 200),
FederatedAuthorization::Direct {
binding: binding(actor.public_key()),
assertion: assertion(principal(), AssertionTransport::TrustedProxy, 200),
},
100,
)
.expect_err("policy evidence from another decision must not finalize");
assert_eq!(error, AuthContextError::FederatedPolicyCorrelationMismatch);
}
#[test]
fn federated_policy_effective_interval_is_half_open() {
let actor = Keys::generate();
let not_yet_effective = AuthContext::finalize_v1(
input(actor.public_key(), AuthTransport::RelayWebSocket, None),
policy_with_lineage(EnrollmentMode::AttestedKey, Uuid::from_u128(2), 101, 200),
FederatedAuthorization::Direct {
binding: binding(actor.public_key()),
assertion: assertion(principal(), AssertionTransport::TrustedProxy, 300),
},
100,
)
.expect_err("policy must not authorize before its effective interval");
assert_eq!(
not_yet_effective,
AuthContextError::FederatedPolicyNotYetEffective
);
let expired = AuthContext::finalize_v1(
input(actor.public_key(), AuthTransport::RelayWebSocket, None),
policy_with_lineage(EnrollmentMode::AttestedKey, Uuid::from_u128(2), 50, 100),
FederatedAuthorization::Direct {
binding: binding(actor.public_key()),
assertion: assertion(principal(), AssertionTransport::TrustedProxy, 300),
},
100,
)
.expect_err("policy must deny at its exact exclusive bound");
assert_eq!(expired, AuthContextError::FederatedPolicyExpired);
}
#[test]
fn federated_policy_stamp_rejects_invalid_lineage() {
let requirement = FederatedIdentityRequirement::Required(EnrollmentMode::Provisioned);
assert_eq!(
FederatedPolicyStamp::from_authoritative_state(
authorization_domain(1),
Uuid::nil(),
1,
Uuid::from_u128(2),
requirement,
1,
200,
),
Err(AuthContextError::InvalidFederatedPolicyId)
);
assert_eq!(
FederatedPolicyStamp::from_authoritative_state(
authorization_domain(1),
Uuid::from_u128(40),
0,
Uuid::from_u128(2),
requirement,
1,
200,
),
Err(AuthContextError::InvalidFederatedPolicyEpoch)
);
assert_eq!(
FederatedPolicyStamp::from_authoritative_state(
authorization_domain(1),
Uuid::from_u128(40),
1,
Uuid::nil(),
requirement,
1,
200,
),
Err(AuthContextError::InvalidFederatedPolicyCorrelation)
);
assert_eq!(
FederatedPolicyStamp::from_authoritative_state(
authorization_domain(1),
Uuid::from_u128(40),
1,
Uuid::from_u128(2),
requirement,
200,
200,
),
Err(AuthContextError::InvalidFederatedPolicyInterval)
);
}
#[test]
fn authoritative_finalizer_derives_binding_reason() {
let existing_actor = Keys::generate();
let existing = AuthContext::finalize_authoritative_v1(
input(
existing_actor.public_key(),
AuthTransport::RelayWebSocket,
None,
),
policy_required(EnrollmentMode::Tofu),
AuthoritativeFederatedResolution::Direct {
binding: AuthoritativeBindingResolution::existing_active(
authoritative_binding_evidence(
existing_actor.public_key(),
BindingSource::Provisioned,
),
),
assertion: assertion(principal(), AssertionTransport::TrustedProxy, 200),
},
100,
)
.expect("existing authoritative binding is eligible");
assert_eq!(
existing.authorization_reason(),
AuthorizationReason::ExistingBinding
);
let attested_actor = Keys::generate();
let attested = AuthContext::finalize_authoritative_v1(
input(
attested_actor.public_key(),
AuthTransport::RelayWebSocket,
None,
),
policy_required(EnrollmentMode::AttestedKey),
AuthoritativeFederatedResolution::Direct {
binding: AuthoritativeBindingResolution::atomically_enrolled(
authoritative_binding_evidence(
attested_actor.public_key(),
BindingSource::AttestedKey,
),
),
assertion: assertion_with_attested_key(
principal(),
AssertionTransport::TrustedProxy,
200,
attested_actor.public_key(),
),
},
100,
)
.expect("attested enrollment result is eligible");
assert_eq!(
attested.authorization_reason(),
AuthorizationReason::EnrolledAttestedKey
);
let tofu_actor = Keys::generate();
let tofu = AuthContext::finalize_authoritative_v1(
input(tofu_actor.public_key(), AuthTransport::RelayWebSocket, None),
policy_required(EnrollmentMode::Tofu),
AuthoritativeFederatedResolution::Direct {
binding: AuthoritativeBindingResolution::atomically_enrolled(
authoritative_binding_evidence(tofu_actor.public_key(), BindingSource::AttestedKey),
),
assertion: assertion_with_attested_key(
principal(),
AssertionTransport::TrustedProxy,
200,
tofu_actor.public_key(),
),
},
100,
)
.expect("stronger attested provenance remains valid under TOFU enrollment");
assert_eq!(
tofu.authorization_reason(),
AuthorizationReason::EnrolledTofu
);
}
#[test]
fn authoritative_finalizer_rejects_incompatible_enrollment_result() {
let actor = Keys::generate();
let error = AuthContext::finalize_authoritative_v1(
input(actor.public_key(), AuthTransport::RelayWebSocket, None),
policy_required(EnrollmentMode::Provisioned),
AuthoritativeFederatedResolution::Direct {
binding: AuthoritativeBindingResolution::atomically_enrolled(
authoritative_binding_evidence(actor.public_key(), BindingSource::Provisioned),
),
assertion: assertion(principal(), AssertionTransport::TrustedProxy, 200),
},
100,
)
.expect_err("ordinary finalization cannot relabel provisioned state as enrollment");
assert_eq!(error, AuthContextError::InvalidAuthorizationReason);
}
#[test]
fn authoritative_finalizer_carries_binding_expiry() {
let actor = Keys::generate();
let evidence = AuthoritativeBindingEvidence::new(
authorization_domain(1),
Uuid::from_u128(10),
principal(),
actor.public_key(),
BindingVersion::INITIAL,
Some(BindingExpiry::new(100).expect("synthetic binding expiry is valid")),
BindingSource::Provisioned,
)
.expect("synthetic authoritative binding evidence is valid");
let error = AuthContext::finalize_authoritative_v1(
input(actor.public_key(), AuthTransport::RelayWebSocket, None),
policy_required(EnrollmentMode::Provisioned),
AuthoritativeFederatedResolution::Direct {
binding: AuthoritativeBindingResolution::existing_active(evidence),
assertion: assertion(principal(), AssertionTransport::TrustedProxy, 200),
},
100,
)
.expect_err("production finalization must preserve the authoritative binding bound");
assert_eq!(error, AuthContextError::BindingExpired);
}
#[test]
fn tofu_enrollment_cannot_use_attested_key_policy_reason() {
let actor = Keys::generate();